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.sequential;
18  
19  import org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.linear.MatrixUtils;
21  import org.hipparchus.linear.RealMatrix;
22  import org.hipparchus.util.FastMath;
23  import org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.Test;
25  import org.orekit.KeyValueFileParser;
26  import org.orekit.Utils;
27  import org.orekit.attitudes.AttitudeProvider;
28  import org.orekit.bodies.CelestialBody;
29  import org.orekit.bodies.OneAxisEllipsoid;
30  import org.orekit.estimation.common.AbstractOrbitDetermination;
31  import org.orekit.estimation.common.ParameterKey;
32  import org.orekit.estimation.common.ResultKalman;
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.OrbitType;
53  import org.orekit.orbits.PositionAngleType;
54  import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
55  import org.orekit.propagation.conversion.ODEIntegratorBuilder;
56  import org.orekit.time.TimeScalesFactory;
57  import org.orekit.utils.IERSConventions;
58  import org.orekit.utils.ParameterDriver;
59  import org.orekit.utils.ParameterDriversList;
60  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
61  
62  import java.io.File;
63  import java.io.IOException;
64  import java.net.URISyntaxException;
65  import java.util.*;
66  
67  public class SequentialNumericalOrbitDeterminationTest extends AbstractOrbitDetermination<NumericalPropagatorBuilder> {
68  
69      /** Gravity field. */
70      private NormalizedSphericalHarmonicsProvider gravityField;
71  
72      /** {@inheritDoc} */
73      @Override
74      protected void createGravityField(final KeyValueFileParser<ParameterKey> parser)
75          throws NoSuchElementException {
76  
77          final int degree = parser.getInt(ParameterKey.CENTRAL_BODY_DEGREE);
78          final int order  = FastMath.min(degree, parser.getInt(ParameterKey.CENTRAL_BODY_ORDER));
79          gravityField = GravityFieldFactory.getNormalizedProvider(degree, order);
80  
81      }
82  
83      /** {@inheritDoc} */
84      @Override
85      protected double getMu() {
86          return gravityField.getMu();
87      }
88  
89      /** {@inheritDoc} */
90      @Override
91      protected NumericalPropagatorBuilder createPropagatorBuilder(final Orbit referenceOrbit,
92                                                                   final ODEIntegratorBuilder builder,
93                                                                   final double positionScale) {
94          return new NumericalPropagatorBuilder(referenceOrbit, builder, PositionAngleType.MEAN,
95                                                positionScale);
96      }
97  
98      /** {@inheritDoc} */
99      @Override
100     protected void setMass(final NumericalPropagatorBuilder propagatorBuilder,
101                                 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     /** {@inheritDoc} */
153     @Override
154     protected List<ParameterDriver> setDrag(final NumericalPropagatorBuilder propagatorBuilder,
155                                             final Atmosphere atmosphere, final DragSensitive spacecraft) {
156         final ForceModel dragModel = new DragForce(atmosphere, spacecraft);
157         propagatorBuilder.addForceModel(dragModel);
158         return dragModel.getParametersDrivers();
159     }
160 
161     /** {@inheritDoc} */
162     @Override
163     protected List<ParameterDriver> setSolarRadiationPressure(final NumericalPropagatorBuilder propagatorBuilder, final CelestialBody sun,
164                                                               final OneAxisEllipsoid earth, final RadiationSensitive spacecraft) {
165         final ForceModel srpModel = new SolarRadiationPressure(sun, earth, spacecraft);
166         propagatorBuilder.addForceModel(srpModel);
167         return srpModel.getParametersDrivers();
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     protected List<ParameterDriver> setAlbedoInfrared(final NumericalPropagatorBuilder propagatorBuilder,
173                                                       final CelestialBody sun, final double equatorialRadius,
174                                                       final double angularResolution,
175                                                       final RadiationSensitive spacecraft) {
176         final ForceModel albedoIR = new KnockeRediffusedForceModel(sun, spacecraft, equatorialRadius, angularResolution);
177         propagatorBuilder.addForceModel(albedoIR);
178         return albedoIR.getParametersDrivers();
179     }
180 
181     /** {@inheritDoc} */
182     @Override
183     protected List<ParameterDriver> setRelativity(final NumericalPropagatorBuilder propagatorBuilder) {
184         final ForceModel relativityModel = new Relativity(gravityField.getMu());
185         propagatorBuilder.addForceModel(relativityModel);
186         return relativityModel.getParametersDrivers();
187     }
188 
189     /** {@inheritDoc} */
190     @Override
191     protected List<ParameterDriver> setPolynomialAcceleration(final NumericalPropagatorBuilder propagatorBuilder,
192                                                               final String name, final Vector3D direction, final int degree) {
193         final AccelerationModel accModel = new PolynomialAccelerationModel(name, null, degree);
194         final ForceModel polynomialModel = new ParametricAcceleration(direction, true, accModel);
195         propagatorBuilder.addForceModel(polynomialModel);
196         return polynomialModel.getParametersDrivers();
197     }
198 
199     /** {@inheritDoc} */
200     @Override
201     protected void setAttitudeProvider(final NumericalPropagatorBuilder propagatorBuilder,
202                                        final AttitudeProvider attitudeProvider) {
203         propagatorBuilder.setAttitudeProvider(attitudeProvider);
204     }
205 
206 
207     @Test
208     public void testLageos2Extended() throws URISyntaxException, IOException {
209 
210         // Position/velocity accuracy
211         final double distanceAccuracy = 2.47;
212         final double velocityAccuracy = 1.1e-3;
213 
214         // Smoother position/velocity accuracy
215         final double smoothDistanceAccuracy = 5.13;
216         final double smoothVelocityAccuracy = 2.6e-3;
217         final double distanceStd = 12.79;
218         final double velocityStd = 6.2e-3;
219 
220         // Batch LS values
221         //final double[] stationOffSet = { 1.659203,  0.861250,  -0.885352 };
222         //final double rangeBias = -0.286275;
223         final double[] stationOffSet = { 0.043893,  0.044721,  -0.037796 };
224         final double rangeBias = 0.041171;
225 
226         // Batch LS values
227         //final double[] refStatRange = { -2.431135, 2.218644, 0.038483, 0.982017 };
228         final double[] refStatRange = { -5.910596, 3.306617, -0.037131, 1.454304 };
229 
230         testLageos2(distanceAccuracy, velocityAccuracy, stationOffSet, rangeBias, refStatRange,
231                 smoothDistanceAccuracy, smoothVelocityAccuracy, distanceStd, velocityStd,
232                 false, false);
233     }
234 
235     @Test
236     public void testLageos2Unscented() throws URISyntaxException, IOException {
237 
238         // Position/velocity accuracy
239         final double distanceAccuracy = 2.46;
240         final double velocityAccuracy = 1.8e-4;
241 
242         // Smoother position/velocity accuracy
243         final double smoothDistanceAccuracy = 3.97;
244         final double smoothVelocityAccuracy = 2.0e-3;
245         final double distanceStd = 19.65;
246         final double velocityStd = 0.011;
247 
248         // Batch LS values
249         //final double[] stationOffSet = { 1.659203,  0.861250,  -0.885352 };
250         //final double rangeBias = -0.286275;
251         final double[] stationOffSet = { 0.044850,  0.030216,  -0.035853 };
252         final double rangeBias = 0.035252;
253 
254         // Batch LS values
255         //final double[] refStatRange = { -2.431135, 2.218644, 0.038483, 0.982017 };
256         final double[] refStatRange = { -6.212086, 3.196686, -0.012196, 1.456780 };
257 
258         testLageos2(distanceAccuracy, velocityAccuracy, stationOffSet, rangeBias, refStatRange,
259                 smoothDistanceAccuracy, smoothVelocityAccuracy, distanceStd, velocityStd,
260                 false, true);
261     }
262 
263 
264     // Orbit determination for Lageos2 based on SLR (range) measurements
265     protected void testLageos2(final double distanceAccuracy, final double velocityAccuracy,
266                                final double[] stationOffSet, final double rangeBias, final double[] refStatRange,
267                                final double smoothDistanceAccuracy, final double smoothVelocityAccuracy,
268                                final double smoothDistanceStd, final double smoothVelocityStd,
269                                final boolean print, final boolean isUnscented) throws URISyntaxException, IOException {
270 
271         // input in resources directory
272         final String inputPath = SequentialNumericalOrbitDeterminationTest.class.getClassLoader()
273                 .getResource("orbit-determination/Lageos2/kalman_od_test_Lageos2.in").toURI().getPath();
274         final File input  = new File(inputPath);
275 
276         // configure Orekit data acces
277         Utils.setDataRoot("orbit-determination/february-2016:potential/icgem-format");
278         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
279 
280         // Choice of an orbit type to use
281         // Default for test is Cartesian
282         final OrbitType orbitType = OrbitType.CARTESIAN;
283 
284         // Cartesian covariance matrix initialization
285         final double posVar = FastMath.pow(1e3, 2);
286         final double velVar = FastMath.pow(1.0, 2);
287         final RealMatrix cartesianOrbitalP = MatrixUtils.createRealDiagonalMatrix(new double [] {
288                 posVar, posVar, posVar, velVar, velVar, velVar
289         });
290 
291         // Orbital Cartesian process noise matrix (Q)
292         final RealMatrix cartesianOrbitalQ = MatrixUtils.createRealMatrix(6, 6);
293 
294         // Initial measurement covariance matrix and process noise matrix
295         final double measVar = FastMath.pow(1.0, 2);
296         final RealMatrix measurementP = MatrixUtils.createRealDiagonalMatrix(new double [] {
297                 measVar, measVar, measVar, measVar
298         });
299         final RealMatrix measurementQ = MatrixUtils.createRealMatrix(4, 4);
300 
301         // Kalman orbit determination run.
302         ResultKalman kalmanLageos2 = runKalman(input, orbitType, print,
303                                                cartesianOrbitalP, cartesianOrbitalQ,
304                                                null, null,
305                                                measurementP, measurementQ, isUnscented);
306 
307         // Tests
308         // Note: The reference initial orbit is the same as in the batch LS tests
309         // -----
310 
311         // Number of measurements processed
312         final int numberOfMeas  = 258;
313         Assertions.assertEquals(numberOfMeas, kalmanLageos2.getNumberOfMeasurements());
314 
315         // Estimated position and velocity
316         final Vector3D estimatedPos = kalmanLageos2.getEstimatedPV().getPosition();
317         final Vector3D estimatedVel = kalmanLageos2.getEstimatedPV().getVelocity();
318 
319         // Reference position and velocity at initial date (same as in batch LS test)
320         final Vector3D refPos0 = new Vector3D(-5532131.956902, 10025696.592156, -3578940.040009);
321         final Vector3D refVel0 = new Vector3D(-3871.275109, -607.880985, 4280.972530);
322 
323         // Run the reference until Kalman last date
324         final Orbit refOrbit = runReference(input, orbitType, refPos0, refVel0, null,
325                                             kalmanLageos2.getEstimatedPV().getDate());
326         final Vector3D refPos = refOrbit.getPosition();
327         final Vector3D refVel = refOrbit.getPVCoordinates().getVelocity();
328 
329         // Check distances
330         final double dP = Vector3D.distance(refPos, estimatedPos);
331         final double dV = Vector3D.distance(refVel, estimatedVel);
332 
333         // Print orbit deltas
334         if (print) {
335             System.out.println("Test performances:");
336             System.out.format("\t%-30s\n",
337                             "ΔEstimated / Reference");
338             System.out.format(Locale.US, "\t%-10s %20.6f\n",
339                               "ΔP [m]", dP);
340             System.out.format(Locale.US, "\t%-10s %20.6f\n",
341                               "ΔV [m/s]", dV);
342         }
343 
344         Assertions.assertEquals(0.0, dP, distanceAccuracy);
345         Assertions.assertEquals(0.0, dV, velocityAccuracy);
346 
347         // Run the reference to initial date
348         final Orbit initialOrbit = runReference(input, orbitType, refPos0, refVel0, null,
349                 kalmanLageos2.getSmoothedState().getDate());
350         final Vector3D initialPos = initialOrbit.getPosition();
351         final Vector3D initialVel = initialOrbit.getPVCoordinates().getVelocity();
352 
353         // Check smoother distances
354         final double[] smoothedState = kalmanLageos2.getSmoothedState().getState().toArray();
355         final Vector3D smoothedPos = new Vector3D(smoothedState[0], smoothedState[1], smoothedState[2]);
356         final Vector3D smoothedVel = new Vector3D(smoothedState[3], smoothedState[4], smoothedState[5]);
357         final double dPSmooth = Vector3D.distance(initialPos, smoothedPos);
358         final double dVSmooth = Vector3D.distance(initialVel, smoothedVel);
359 
360         // Check smoother variances
361         final RealMatrix smoothedCov = kalmanLageos2.getSmoothedState().getCovarianceMatrix();
362         final double posStd = FastMath.sqrt(smoothedCov.getEntry(0, 0) +
363                 smoothedCov.getEntry(1, 1) + smoothedCov.getEntry(2, 2));
364         final double velStd = FastMath.sqrt(smoothedCov.getEntry(3, 3) +
365                 smoothedCov.getEntry(4, 4) + smoothedCov.getEntry(5, 5));
366 
367         // Print smoother orbit deltas
368         if (print) {
369             System.out.println("Smoother performances:");
370             System.out.format("\t%-30s\n",
371                     "ΔEstimated / Reference & std. dev.");
372             System.out.format(Locale.US, "\t%-10s %20.6f %20.6f\n",
373                     "ΔP [m]", dPSmooth, posStd);
374             System.out.format(Locale.US, "\t%-10s %20.6f %20.6f\n",
375                     "ΔV [m/s]", dVSmooth, velStd);
376         }
377 
378         Assertions.assertEquals(0.0, dPSmooth, smoothDistanceAccuracy);
379         Assertions.assertEquals(0.0, dVSmooth, smoothVelocityAccuracy);
380         Assertions.assertEquals(0.0, posStd, smoothDistanceStd);
381         Assertions.assertEquals(0.0, velStd, smoothVelocityStd);
382 
383 
384         // Accuracy for tests
385         final double parametersAccuracy = 1e-6;
386 
387         // Test on measurements parameters
388         final List<DelegatingDriver> list = new ArrayList<>(kalmanLageos2.getMeasurementsParameters().getDrivers());
389         sortParametersChanges(list);
390 
391         Assertions.assertEquals(stationOffSet[0], list.get(0).getValue(), parametersAccuracy);
392         Assertions.assertEquals(stationOffSet[1], list.get(1).getValue(), parametersAccuracy);
393         Assertions.assertEquals(stationOffSet[2], list.get(2).getValue(), parametersAccuracy);
394         Assertions.assertEquals(rangeBias,        list.get(3).getValue(), parametersAccuracy);
395 
396         //test on statistic for the range residuals
397         final long nbRange = 258;
398         Assertions.assertEquals(nbRange, kalmanLageos2.getRangeStat().getN());
399         Assertions.assertEquals(refStatRange[0], kalmanLageos2.getRangeStat().getMin(),               parametersAccuracy);
400         Assertions.assertEquals(refStatRange[1], kalmanLageos2.getRangeStat().getMax(),               parametersAccuracy);
401         Assertions.assertEquals(refStatRange[2], kalmanLageos2.getRangeStat().getMean(),              parametersAccuracy);
402         Assertions.assertEquals(refStatRange[3], kalmanLageos2.getRangeStat().getStandardDeviation(), parametersAccuracy);
403 
404     }
405 
406     @Test
407     public void testW3BExtended() throws URISyntaxException, IOException {
408         // Batch LS result: -0.2154;
409         final double dragCoef  = 2.0010;
410 
411         // Batch LS results: 8.002e-6
412         final double leakAccelerationNorm0 = 2.7e-9;
413 
414         // Batch LS results: 3.058e-10
415         final double leakAccelerationNorm1 = 8.1e-13;
416 
417         // Batch LS results
418         // final double[] CastleAzElBias  = { 0.062701342, -0.003613508 };
419         // final double   CastleRangeBias = 11274.4677;
420         final double[] castleAzElBias  = { 0.086136, -0.032682};
421         final double   castleRangeBias = 11473.6163;
422 
423         // Batch LS results
424         // final double[] FucAzElBias  = { -0.053526137, 0.075483886 };
425         // final double   FucRangeBias = 13467.8256;
426         final double[] fucAzElBias  = { -0.067443, 0.064581 };
427         final double   fucRangeBias = 13468.9624;
428 
429         // Batch LS results
430         // final double[] KumAzElBias  = { -0.023574208, -0.054520756 };
431         // final double   KumRangeBias = 13512.57594;
432         final double[] kumAzElBias  = { -0.000102, -0.066097 };
433         final double   kumRangeBias = 13527.0004;
434 
435         // Batch LS results
436         // final double[] PreAzElBias = { 0.030201539, 0.009747877 };
437         // final double PreRangeBias = 13594.11889;
438         final double[] preAzElBias = { 0.029973, 0.011140 };
439         final double   preRangeBias = 13370.1890;
440 
441         // Batch LS results
442         // final double[] UraAzElBias = { 0.167814449, -0.12305252 };
443         // final double UraRangeBias = 13450.26738;
444         final double[] uraAzElBias = { 0.148459, -0.138353 };
445         final double   uraRangeBias = 13314.9300;
446 
447         //statistics for the range residual (min, max, mean, std)
448         final double[] refStatRange = { -0.5948, 35.4751, 0.3061, 2.7790 };
449 
450         //statistics for the azimuth residual (min, max, mean, std)
451         final double[] refStatAzi = { -0.024691, 0.020452, -0.001111, 0.006750 };
452 
453         //statistics for the elevation residual (min, max, mean, std)
454         final double[] refStatEle = { -0.030255, 0.026288, 0.002044, 0.007260 };
455 
456         // Expected covariance
457         final double dragVariance = 0.999722;
458         final double leakXVariance = 0.9999e-12;
459         final double leakYVariance = 1e-12;
460         final double leakZVariance = 1e-12;
461 
462         // Prediction position/velocity accuracies
463         // FIXME: debug - Comparison with batch LS is bad
464         final double predictionDistanceAccuracy = 2127.851;
465         final double predictionVelocityAccuracy = 1.073;
466 
467         testW3B(dragCoef, leakAccelerationNorm0, leakAccelerationNorm1,
468                 castleAzElBias, castleRangeBias, fucAzElBias, fucRangeBias, kumAzElBias, kumRangeBias,
469                 preAzElBias, preRangeBias, uraAzElBias, uraRangeBias,
470                 refStatRange, refStatAzi, refStatEle, dragVariance,
471                 leakXVariance, leakYVariance, leakZVariance,
472                 predictionDistanceAccuracy, predictionVelocityAccuracy, false, false);
473     }
474 
475     // Orbit determination for range, azimuth elevation measurements
476     protected void testW3B(final double dragCoef, final double leakAccelerationNorm0, final double leakAccelerationNorm1,
477                            final double[] castleAzElBias, final double castleRangeBias,
478                            final double[] fucAzElBias, final double fucRangeBias,
479                            final double[] kumAzElBias, final double kumRangeBias,
480                            final double[] preAzElBias, final double preRangeBias,
481                            final double[] uraAzElBias, final double uraRangeBias,
482                            final double[] refStatRange, final double[] refStatAzi, final double[] refStatEle,
483                            final double dragVariance,
484                            final double leakXVariance, final double leakYVariance, final double leakZVariance,
485                            final double predictionDistanceAccuracy, final double predictionVelocityAccuracy,
486                            final boolean print, final boolean isUnscented) throws URISyntaxException, IOException {
487 
488         // input in resources directory
489         final String inputPath = SequentialNumericalOrbitDeterminationTest.class.getClassLoader().getResource("orbit-determination/W3B/od_test_W3.in").toURI().getPath();
490         final File input  = new File(inputPath);
491 
492         // Configure Orekit data access
493         Utils.setDataRoot("orbit-determination/W3B:potential/icgem-format");
494         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
495 
496         // Choice of an orbit type to use
497         // Default for test is Cartesian
498         final OrbitType orbitType = OrbitType.CARTESIAN;
499 
500         // Initial orbital Cartesian covariance matrix
501         final double positionP = FastMath.pow(1e4, 2);
502         final double velocityP = FastMath.pow(10.0, 2);
503         final RealMatrix cartesianOrbitalP = MatrixUtils.createRealDiagonalMatrix(new double [] {
504                 positionP, positionP, positionP, velocityP, velocityP, velocityP
505         });
506 
507         // Orbital Cartesian process noise matrix (Q)
508         final double positionQ = FastMath.pow(100.0, 2);
509         final double velocityQ = FastMath.pow(1.0, 2);
510         final RealMatrix cartesianOrbitalQ = MatrixUtils.createRealDiagonalMatrix(new double [] {
511                 positionQ, positionQ, positionQ, velocityQ, velocityQ, velocityQ
512         });
513 
514         // Propagation covariance and process noise matrices
515         final RealMatrix propagationP = MatrixUtils.createRealDiagonalMatrix(new double [] {
516                 FastMath.pow(1.0, 2), // Cd
517                 FastMath.pow(1e-6, 2), FastMath.pow(1e-10, 2),   // leak-X
518                 FastMath.pow(1e-6, 2), FastMath.pow(1e-10, 2), // leak-Y
519                 FastMath.pow(1e-6, 2), FastMath.pow(1e-10, 2)  // leak-Z
520         });
521         final RealMatrix propagationQ = MatrixUtils.createRealMatrix(7, 7);
522 
523         // Measurement covariance and process noise matrices
524         final double angularVariance = FastMath.pow(1e-2, 2);
525         final double rangeVariance   = FastMath.pow(10.0, 2);
526         final RealMatrix measurementP = MatrixUtils.createRealDiagonalMatrix(new double [] {
527                 angularVariance, angularVariance, rangeVariance,
528                 angularVariance, angularVariance, rangeVariance,
529                 angularVariance, angularVariance, rangeVariance,
530                 angularVariance, angularVariance, rangeVariance,
531                 angularVariance, angularVariance, rangeVariance,
532         });
533         final RealMatrix measurementQ = MatrixUtils.createRealMatrix(15, 15);
534 
535         // Kalman orbit determination run.
536         ResultKalman kalmanW3B = runKalman(input, orbitType, print,
537                                            cartesianOrbitalP, cartesianOrbitalQ,
538                                            propagationP, propagationQ,
539                                            measurementP, measurementQ, isUnscented);
540 
541         // Tests
542         // -----
543 
544         // Definition of the accuracy for the test
545         final double distanceAccuracy = 1e-4;
546         final double angleAccuracy    = 1e-6; // degrees
547 
548         // Number of measurements processed
549         final int numberOfMeas  = 521;
550         Assertions.assertEquals(numberOfMeas, kalmanW3B.getNumberOfMeasurements());
551 
552 
553         // Test on propagator parameters
554         // -----------------------------
555         final ParameterDriversList propagatorParameters = kalmanW3B.getPropagatorParameters();
556         Assertions.assertEquals(dragCoef, propagatorParameters.getDrivers().get(0).getValue(), 1e-4);
557         final Vector3D leakAcceleration0 =
558                         new Vector3D(propagatorParameters.getDrivers().get(1).getValue(),
559                                      propagatorParameters.getDrivers().get(3).getValue(),
560                                      propagatorParameters.getDrivers().get(5).getValue());
561         Assertions.assertEquals(leakAccelerationNorm0, leakAcceleration0.getNorm(), 1.0e-9);
562 
563         final Vector3D leakAcceleration1 =
564                         new Vector3D(propagatorParameters.getDrivers().get(2).getValue(),
565                                      propagatorParameters.getDrivers().get(4).getValue(),
566                                      propagatorParameters.getDrivers().get(6).getValue());
567         Assertions.assertEquals(leakAccelerationNorm1, leakAcceleration1.getNorm(), 1.0e-13);
568 
569         // Test on measurements parameters
570         // -------------------------------
571 
572         final List<DelegatingDriver> list = new ArrayList<>(kalmanW3B.getMeasurementsParameters().getDrivers());
573         sortParametersChanges(list);
574 
575         // Station CastleRock
576         Assertions.assertEquals(castleAzElBias[0], FastMath.toDegrees(list.get(0).getValue()), angleAccuracy);
577         Assertions.assertEquals(castleAzElBias[1], FastMath.toDegrees(list.get(1).getValue()), angleAccuracy);
578         Assertions.assertEquals(castleRangeBias,   list.get(2).getValue(),                     distanceAccuracy);
579 
580         // Station Fucino
581         Assertions.assertEquals(fucAzElBias[0], FastMath.toDegrees(list.get(3).getValue()), angleAccuracy);
582         Assertions.assertEquals(fucAzElBias[1], FastMath.toDegrees(list.get(4).getValue()), angleAccuracy);
583         Assertions.assertEquals(fucRangeBias,   list.get(5).getValue(),                     distanceAccuracy);
584 
585         // Station Kumsan
586         Assertions.assertEquals(kumAzElBias[0], FastMath.toDegrees(list.get(6).getValue()), angleAccuracy);
587         Assertions.assertEquals(kumAzElBias[1], FastMath.toDegrees(list.get(7).getValue()), angleAccuracy);
588         Assertions.assertEquals(kumRangeBias,   list.get(8).getValue(),                     distanceAccuracy);
589 
590         // Station Pretoria
591         Assertions.assertEquals(preAzElBias[0], FastMath.toDegrees(list.get( 9).getValue()), angleAccuracy);
592         Assertions.assertEquals(preAzElBias[1], FastMath.toDegrees(list.get(10).getValue()), angleAccuracy);
593         Assertions.assertEquals(preRangeBias,   list.get(11).getValue(),                     distanceAccuracy);
594 
595         // Station Uralla
596         Assertions.assertEquals(uraAzElBias[0], FastMath.toDegrees(list.get(12).getValue()), angleAccuracy);
597         Assertions.assertEquals(uraAzElBias[1], FastMath.toDegrees(list.get(13).getValue()), angleAccuracy);
598         Assertions.assertEquals(uraRangeBias,   list.get(14).getValue(),                     distanceAccuracy);
599 
600 
601         // Test on statistic for the range residuals
602         final long nbRange = 182;
603         Assertions.assertEquals(nbRange, kalmanW3B.getRangeStat().getN());
604         Assertions.assertEquals(refStatRange[0], kalmanW3B.getRangeStat().getMin(),               distanceAccuracy);
605         Assertions.assertEquals(refStatRange[1], kalmanW3B.getRangeStat().getMax(),               distanceAccuracy);
606         Assertions.assertEquals(refStatRange[2], kalmanW3B.getRangeStat().getMean(),              distanceAccuracy);
607         Assertions.assertEquals(refStatRange[3], kalmanW3B.getRangeStat().getStandardDeviation(), distanceAccuracy);
608 
609         //test on statistic for the azimuth residuals
610         final long nbAzi = 339;
611         Assertions.assertEquals(nbAzi, kalmanW3B.getAzimStat().getN());
612         Assertions.assertEquals(refStatAzi[0], kalmanW3B.getAzimStat().getMin(),               angleAccuracy);
613         Assertions.assertEquals(refStatAzi[1], kalmanW3B.getAzimStat().getMax(),               angleAccuracy);
614         Assertions.assertEquals(refStatAzi[2], kalmanW3B.getAzimStat().getMean(),              angleAccuracy);
615         Assertions.assertEquals(refStatAzi[3], kalmanW3B.getAzimStat().getStandardDeviation(), angleAccuracy);
616 
617         //test on statistic for the elevation residuals
618         final long nbEle = 339;
619         Assertions.assertEquals(nbEle, kalmanW3B.getElevStat().getN());
620         Assertions.assertEquals(refStatEle[0], kalmanW3B.getElevStat().getMin(),               angleAccuracy);
621         Assertions.assertEquals(refStatEle[1], kalmanW3B.getElevStat().getMax(),               angleAccuracy);
622         Assertions.assertEquals(refStatEle[2], kalmanW3B.getElevStat().getMean(),              angleAccuracy);
623         Assertions.assertEquals(refStatEle[3], kalmanW3B.getElevStat().getStandardDeviation(), angleAccuracy);
624 
625         RealMatrix covariances = kalmanW3B.getCovariances();
626         Assertions.assertEquals(28, covariances.getRowDimension());
627         Assertions.assertEquals(28, covariances.getColumnDimension());
628 
629         // drag coefficient variance
630         Assertions.assertEquals(dragVariance, covariances.getEntry(6, 6), 1.0e-6);
631 
632         // leak-X constant term variance
633         Assertions.assertEquals(leakXVariance, covariances.getEntry(7, 7), 1.0e-16);
634 
635         // leak-Y constant term variance
636         Assertions.assertEquals(leakYVariance, covariances.getEntry(9, 9), 1.0e-16);
637 
638         // leak-Z constant term variance
639         Assertions.assertEquals(leakZVariance, covariances.getEntry(11, 11), 1.0e-16);
640 
641         // Test on orbital parameters
642         // Done at the end to avoid changing the estimated propagation parameters
643         // ----------------------------------------------------------------------
644 
645         // Estimated position and velocity
646         final Vector3D estimatedPos = kalmanW3B.getEstimatedPV().getPosition();
647         final Vector3D estimatedVel = kalmanW3B.getEstimatedPV().getVelocity();
648 
649         // Reference position and velocity at initial date (same as in batch LS test)
650         final Vector3D refPos0 = new Vector3D(-40541446.255, -9905357.41, 206777.413);
651         final Vector3D refVel0 = new Vector3D(759.0685, -1476.5156, 54.793);
652 
653         // Gather the selected propagation parameters and initialize them to the values found
654         // with the batch LS method
655         final ParameterDriversList refPropagationParameters = kalmanW3B.getPropagatorParameters();
656         final double dragCoefRef = -0.215433133145843;
657         final double[] leakXRef = {+5.69040439901955E-06, 1.09710906802403E-11};
658         final double[] leakYRef = {-7.66440256777678E-07, 1.25467464335066E-10};
659         final double[] leakZRef = {-5.574055079952E-06  , 2.78703463746911E-10};
660 
661         for (DelegatingDriver driver : refPropagationParameters.getDrivers()) {
662             switch (driver.getName()) {
663                 case "drag coefficient" : driver.setValue(dragCoefRef); break;
664                 case "leak-X[0]"        : driver.setValue(leakXRef[0]); break;
665                 case "leak-X[1]"        : driver.setValue(leakXRef[1]); break;
666                 case "leak-Y[0]"        : driver.setValue(leakYRef[0]); break;
667                 case "leak-Y[1]"        : driver.setValue(leakYRef[1]); break;
668                 case "leak-Z[0]"        : driver.setValue(leakZRef[0]); break;
669                 case "leak-Z[1]"        : driver.setValue(leakZRef[1]); break;
670             }
671         }
672 
673         // Run the reference until Kalman last date
674         final Orbit refOrbit = runReference(input, orbitType, refPos0, refVel0, refPropagationParameters,
675                                             kalmanW3B.getEstimatedPV().getDate());
676 
677         // Test on last orbit
678         final Vector3D refPos = refOrbit.getPosition();
679         final Vector3D refVel = refOrbit.getPVCoordinates().getVelocity();
680 
681         // Check distances
682         final double dP = Vector3D.distance(refPos, estimatedPos);
683         final double dV = Vector3D.distance(refVel, estimatedVel);
684 
685         // Print orbit deltas
686         if (print) {
687             System.out.println("Test performances:");
688             System.out.format("\t%-30s\n",
689                             "ΔEstimated / Reference");
690             System.out.format(Locale.US, "\t%-10s %20.6f\n",
691                               "ΔP [m]", dP);
692             System.out.format(Locale.US, "\t%-10s %20.6f\n",
693                               "ΔV [m/s]", dV);
694         }
695 
696         Assertions.assertEquals(0.0, dP, predictionDistanceAccuracy);
697         Assertions.assertEquals(0.0, dV, predictionVelocityAccuracy);
698 
699     }
700 
701 }