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