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.measurements;
18  
19  import java.util.List;
20  
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.linear.MatrixUtils;
23  import org.hipparchus.stat.descriptive.StreamingStatistics;
24  import org.hipparchus.stat.descriptive.rank.Median;
25  import org.hipparchus.util.FastMath;
26  import org.junit.jupiter.api.Assertions;
27  import org.junit.jupiter.api.Test;
28  import org.orekit.errors.OrekitException;
29  import org.orekit.estimation.Context;
30  import org.orekit.estimation.EstimationTestUtils;
31  import org.orekit.orbits.OrbitType;
32  import org.orekit.orbits.PositionAngleType;
33  import org.orekit.propagation.Propagator;
34  import org.orekit.propagation.SpacecraftState;
35  import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
36  import org.orekit.time.AbsoluteDate;
37  import org.orekit.utils.Differentiation;
38  
39  public class PVTest {
40  
41      /** Compare observed values and estimated values.
42       *  Both are calculated with a different algorithm
43       */
44      @Test
45      public void testValues() {
46  
47          Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
48  
49          final NumericalPropagatorBuilder propagatorBuilder =
50                          context.createBuilder(OrbitType.EQUINOCTIAL, PositionAngleType.TRUE, false,
51                                                1.0e-6, 60.0, 0.001);
52  
53          // Create perfect right-ascension/declination measurements
54          final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
55                                                                             propagatorBuilder);
56          final List<ObservedMeasurement<?>> measurements =
57                          EstimationTestUtils.createMeasurements(propagator,
58                                                                 new PVMeasurementCreator(),
59                                                                 1.0, 3.0, 300.0);
60  
61          propagator.clearStepHandlers();
62  
63          // Prepare statistics for PV values difference
64          final StreamingStatistics[] pvDiffStat = new StreamingStatistics[6];
65          for (int i = 0; i < 6; i++) {
66              pvDiffStat[i] = new StreamingStatistics();
67          }
68  
69          for (final ObservedMeasurement<?> measurement : measurements) {
70  
71              // Propagate to measurement date
72              final AbsoluteDate datemeas  = measurement.getDate();
73              SpacecraftState    state     = propagator.propagate(datemeas);
74  
75              // Estimate the PV value
76              final EstimatedMeasurementBase<?> estimated = measurement.estimateWithoutDerivatives(new SpacecraftState[] { state });
77  
78              // Store the difference between estimated and observed values in the stats
79              for (int i = 0; i < 6; i++) {
80                  pvDiffStat[i].addValue(FastMath.abs(estimated.getEstimatedValue()[i] - measurement.getObservedValue()[i]));
81              }
82          }
83  
84          // Mean and std errors check
85          for (int i = 0; i < 3; i++) {
86              // Check position values
87              Assertions.assertEquals(0.0, pvDiffStat[i].getMean(), 3.74e-7);
88              Assertions.assertEquals(0.0, pvDiffStat[i].getStandardDeviation(), 2.21e-7);
89  
90              // Check velocity values
91              Assertions.assertEquals(0.0, pvDiffStat[i+3].getMean(), 1.29e-10);
92              Assertions.assertEquals(0.0, pvDiffStat[i+3].getStandardDeviation(), 7.82e-11);
93          }
94  
95          // Test measurement type
96          Assertions.assertEquals(PV.MEASUREMENT_TYPE, measurements.get(0).getMeasurementType());
97      }
98  
99      /** Test the values of the state derivatives using a numerical.
100      * finite differences calculation as a reference
101      */
102     @Test
103     public void testStateDerivatives() {
104 
105         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
106 
107         final NumericalPropagatorBuilder propagatorBuilder =
108                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
109                                               1.0e-6, 60.0, 0.001);
110 
111         // create perfect range measurements
112         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
113                                                                            propagatorBuilder);
114         final List<ObservedMeasurement<?>> measurements =
115                         EstimationTestUtils.createMeasurements(propagator,
116                                                                new PVMeasurementCreator(),
117                                                                1.0, 3.0, 300.0);
118         propagator.clearStepHandlers();
119 
120         double[] errorsP = new double[3 * 6 * measurements.size()];
121         double[] errorsV = new double[3 * 6 * measurements.size()];
122         int indexP = 0;
123         int indexV = 0;
124         for (final ObservedMeasurement<?> measurement : measurements) {
125 
126             final AbsoluteDate    date      = measurement.getDate();
127             final SpacecraftState state     = propagator.propagate(date);
128             final double[][]      jacobian  = measurement.estimate(0, 0, new SpacecraftState[] { state }).getStateDerivatives(0);
129 
130             // compute a reference value using finite differences
131             final double[][] finiteDifferencesJacobian =
132                 Differentiation.differentiate(state1 -> measurement.
133                        estimateWithoutDerivatives(new SpacecraftState[] { state1 }).
134                        getEstimatedValue(), measurement.getDimension(),
135                                               propagator.getAttitudeProvider(), OrbitType.CARTESIAN,
136                                               PositionAngleType.TRUE, 1.0, 3).value(state);
137 
138             Assertions.assertEquals(finiteDifferencesJacobian.length, jacobian.length);
139             Assertions.assertEquals(finiteDifferencesJacobian[0].length, jacobian[0].length);
140             for (int i = 0; i < jacobian.length; ++i) {
141                 for (int j = 0; j < jacobian[i].length; ++j) {
142                     final double relativeError = FastMath.abs((finiteDifferencesJacobian[i][j] - jacobian[i][j]) /
143                                                               finiteDifferencesJacobian[i][j]);
144                     if (j < 3) {
145                         errorsP[indexP++] = relativeError;
146                     } else {
147                         errorsV[indexV++] = relativeError;
148                     }
149                 }
150             }
151 
152         }
153 
154         // median errors
155         Assertions.assertEquals(0.0, new Median().evaluate(errorsP), 2.1e-10);
156         Assertions.assertEquals(0.0, new Median().evaluate(errorsV), 2.1e-10);
157 
158     }
159 
160     /** Test the PV constructor with standard deviations for position and velocity given as 2 double.
161      */
162     @Test
163     public void testPVWithSingleStandardDeviations() {
164 
165         // Context
166         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
167 
168         // Dummy P, V, T
169         final Vector3D     position = context.initialOrbit.getPosition();
170         final Vector3D     velocity = context.initialOrbit.getPVCoordinates().getVelocity();
171         final AbsoluteDate date     = context.initialOrbit.getDate();
172 
173         // Initialize standard deviations and weight
174         final double sigmaP     = 10.;
175         final double sigmaV     = 0.1;
176         final double baseWeight = 0.5;
177 
178         // Reference covariance matrix and correlation coefficients
179         final double[][] Pref = new double[6][6];
180         for (int i = 0; i < 3; i++) {
181             Pref[i][i]     = FastMath.pow(sigmaP, 2);
182             Pref[i+3][i+3] = FastMath.pow(sigmaV, 2);
183         }
184         final double[][] corrCoefRef = MatrixUtils.createRealIdentityMatrix(6).getData();
185 
186         // Reference propagator numbers
187         final ObservableSatellite[] sats = {
188             new ObservableSatellite(0),
189             new ObservableSatellite(2)
190         };
191 
192         // Create PV measurements
193         final PV[] pvs = new PV[2];
194         pvs[0] = new PV(date, position, velocity, sigmaP, sigmaV, baseWeight, sats[0]);
195         pvs[1] = new PV(date, position, velocity, sigmaP, sigmaV, baseWeight, sats[1]);
196 
197         // Tolerance
198         final double eps = 1e-20; // tolerance
199 
200         // Check data
201         for (int k = 0; k < pvs.length; k++) {
202             final PV pv = pvs[k];
203 
204             // Propagator numbers
205             Assertions.assertEquals(sats[k].getPropagatorIndex(), pv.getSatellites().get(0).getPropagatorIndex());
206 
207             // Weights
208             for (int i = 0; i < 6; i++) {
209                 Assertions.assertEquals(baseWeight, pv.getBaseWeight()[i], eps);
210             }
211             // Sigmas
212             for (int i = 0; i < 3; i++) {
213                 Assertions.assertEquals(sigmaP, pv.getTheoreticalStandardDeviation()[i]  , eps);
214                 Assertions.assertEquals(sigmaV, pv.getTheoreticalStandardDeviation()[i+3], eps);
215             }
216             // Covariances
217             final double[][] P = pv.getCovarianceMatrix();
218             // Substract with ref and get the norm
219             final double normP = MatrixUtils.createRealMatrix(P).subtract(MatrixUtils.createRealMatrix(Pref)).getNorm1();
220             Assertions.assertEquals(0., normP, eps);
221 
222             // Correlation coef
223             final double[][] corrCoef = pv.getCorrelationCoefficientsMatrix();
224             // Substract with ref and get the norm
225             final double normCorrCoef = MatrixUtils.createRealMatrix(corrCoef).subtract(MatrixUtils.createRealMatrix(corrCoefRef)).getNorm1();
226             Assertions.assertEquals(0., normCorrCoef, eps);
227         }
228     }
229 
230     /** Test the PV constructor with standard deviations for position and velocity given as a 6-sized vector.
231      */
232     @Test
233     public void testPVWithVectorStandardDeviations() {
234 
235         // Context
236         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
237 
238         // Dummy P, V, T
239         final Vector3D     position = context.initialOrbit.getPosition();
240         final Vector3D     velocity = context.initialOrbit.getPVCoordinates().getVelocity();
241         final AbsoluteDate date     = context.initialOrbit.getDate();
242 
243         // Initialize standard deviations and weight
244         final double[] sigmaP     = {10., 20., 30.};
245         final double[] sigmaV     = {0.1, 0.2, 0.3};
246         final double[] sigmaPV    = {10., 20., 30., 0.1, 0.2, 0.3};
247         final double baseWeight = 0.5;
248 
249         // Reference covariance matrix and correlation coefficients
250         final double[][] Pref = new double[6][6];
251         for (int i = 0; i < 3; i++) {
252             Pref[i][i]     = FastMath.pow(sigmaP[i], 2);
253             Pref[i+3][i+3] = FastMath.pow(sigmaV[i], 2);
254         }
255         final double[][] corrCoefRef = MatrixUtils.createRealIdentityMatrix(6).getData();
256 
257         // Reference propagator numbers
258         final ObservableSatellite[] sats = {
259             new ObservableSatellite(0),
260             new ObservableSatellite(2),
261             new ObservableSatellite(0),
262             new ObservableSatellite(10)
263         };
264 
265         // Create PV measurements
266         final PV[] pvs = new PV[4];
267         pvs[0] = new PV(date, position, velocity, sigmaP, sigmaV, baseWeight, sats[0]);
268         pvs[1] = new PV(date, position, velocity, sigmaP, sigmaV, baseWeight, sats[1]);
269         pvs[2] = new PV(date, position, velocity, sigmaPV, baseWeight, sats[2]);
270         pvs[3] = new PV(date, position, velocity, sigmaPV, baseWeight, sats[3]);
271 
272         // Tolerance
273         final double eps = 1e-20; // tolerance
274 
275         // Check data
276         for (int k = 0; k < pvs.length; k++) {
277             final PV pv = pvs[k];
278 
279             // Propagator numbers
280             Assertions.assertEquals(sats[k].getPropagatorIndex(), pv.getSatellites().get(0).getPropagatorIndex());
281 
282             // Weights
283             for (int i = 0; i < 6; i++) {
284                 Assertions.assertEquals(baseWeight, pv.getBaseWeight()[i], eps);
285             }
286             // Sigmas
287             for (int i = 0; i < 3; i++) {
288                 Assertions.assertEquals(sigmaP[i], pv.getTheoreticalStandardDeviation()[i]  , eps);
289                 Assertions.assertEquals(sigmaV[i], pv.getTheoreticalStandardDeviation()[i+3], eps);
290             }
291             // Covariances
292             final double[][] P = pv.getCovarianceMatrix();
293             // Substract with ref and get the norm
294             final double normP = MatrixUtils.createRealMatrix(P).subtract(MatrixUtils.createRealMatrix(Pref)).getNorm1();
295             Assertions.assertEquals(0., normP, eps);
296 
297             // Correlation coef
298             final double[][] corrCoef = pv.getCorrelationCoefficientsMatrix();
299             // Substract with ref and get the norm
300             final double normCorrCoef = MatrixUtils.createRealMatrix(corrCoef).subtract(MatrixUtils.createRealMatrix(corrCoefRef)).getNorm1();
301             Assertions.assertEquals(0., normCorrCoef, eps);
302         }
303     }
304 
305     /** Test the PV constructor with two 3x3 covariance matrix (one for position, the other for velocity) as input.
306      */
307     @Test
308     public void testPVWithTwoCovarianceMatrices() {
309         // Context
310         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
311 
312         // Dummy P, V, T
313         final Vector3D     position = context.initialOrbit.getPosition();
314         final Vector3D     velocity = context.initialOrbit.getPVCoordinates().getVelocity();
315         final AbsoluteDate date     = context.initialOrbit.getDate();
316 
317         // Initialize standard deviations and weight
318         final double[][] positionP = {{100., 400., 1200.}, {400., 400., 1800.}, {1200., 1800., 900.}};
319         final double[][] velocityP = {{0.01, 0.04, 0.12 }, {0.04, 0.04, 0.18 }, {0.12 , 0.18 , 0.09}};
320         final double baseWeight = 0.5;
321 
322         // Reference covariance matrix and correlation coefficients
323         final double[][] Pref = new double[6][6];
324         for (int i = 0; i < 3; i++) {
325             for (int j = i; j < 3; j++) {
326                 Pref[i][j]     = positionP[i][j];
327                 Pref[j][i]     = positionP[i][j];
328                 Pref[i+3][j+3] = velocityP[i][j];
329                 Pref[j+3][i+3] = velocityP[i][j];
330             }
331         }
332         final double[][] corrCoefRef3 = {{1., 2., 4.}, {2., 1., 3.}, {4., 3., 1.}};
333         final double[][] corrCoefRef  = new double[6][6];
334         for (int i = 0; i < 3; i++) {
335             for (int j = i; j < 3; j++) {
336                 corrCoefRef[i][j]     = corrCoefRef3[i][j];
337                 corrCoefRef[j][i]     = corrCoefRef3[i][j];
338                 corrCoefRef[i+3][j+3] = corrCoefRef3[i][j];
339                 corrCoefRef[j+3][i+3] = corrCoefRef3[i][j];
340             }
341         }
342 
343         // Reference propagator numbers
344         final ObservableSatellite[] sats = {
345             new ObservableSatellite(0),
346             new ObservableSatellite(2)
347         };
348 
349         // Reference standard deviations
350         final double[] sigmaP = {10., 20., 30.};
351         final double[] sigmaV = {0.1, 0.2, 0.3};
352 
353         // Create PV measurements
354         final PV[] pvs = new PV[2];
355         pvs[0] = new PV(date, position, velocity, positionP, velocityP, baseWeight, sats[0]);
356         pvs[1] = new PV(date, position, velocity, positionP, velocityP, baseWeight, sats[1]);
357 
358         // Tolerance
359         final double eps = 6.7e-16; // tolerance
360 
361         // Check data
362         for (int k = 0; k < pvs.length; k++) {
363             final PV pv = pvs[k];
364 
365             // Propagator numbers
366             Assertions.assertEquals(sats[k].getPropagatorIndex(), pv.getSatellites().get(0).getPropagatorIndex());
367 
368             // Weights
369             for (int i = 0; i < 6; i++) {
370                 Assertions.assertEquals(baseWeight, pv.getBaseWeight()[i], eps);
371             }
372             // Sigmas
373             for (int i = 0; i < 3; i++) {
374                 Assertions.assertEquals(sigmaP[i], pv.getTheoreticalStandardDeviation()[i]  , eps);
375                 Assertions.assertEquals(sigmaV[i], pv.getTheoreticalStandardDeviation()[i+3], eps);
376             }
377             // Covariances
378             final double[][] P = pv.getCovarianceMatrix();
379             // Substract with ref and get the norm
380             final double normP = MatrixUtils.createRealMatrix(P).subtract(MatrixUtils.createRealMatrix(Pref)).getNorm1();
381             Assertions.assertEquals(0., normP, eps);
382 
383             // Correlation coef
384             final double[][] corrCoef = pv.getCorrelationCoefficientsMatrix();
385             // Substract with ref and get the norm
386             final double normCorrCoef = MatrixUtils.createRealMatrix(corrCoef).subtract(MatrixUtils.createRealMatrix(corrCoefRef)).getNorm1();
387             Assertions.assertEquals(0., normCorrCoef, eps);
388         }
389 
390     }
391 
392     /** Test the PV constructor with one 6x6 covariance matrix as input.
393      */
394     @Test
395     public void testPVWithCovarianceMatrix() {
396         // Context
397         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
398 
399         // Dummy P, V, T
400         final Vector3D     position = context.initialOrbit.getPosition();
401         final Vector3D     velocity = context.initialOrbit.getPVCoordinates().getVelocity();
402         final AbsoluteDate date     = context.initialOrbit.getDate();
403 
404         // Initialize standard deviations, weight and corr coeff
405         final double[] sigmaPV = {10., 20., 30., 0.1, 0.2, 0.3};
406         final double baseWeight = 0.5;
407         final double[][] corrCoefRef = new double[6][6];
408         for (int i = 0; i < 6; i++) {
409             for (int j = 0; j < 6; j++) {
410                 if (i == j) {
411                     corrCoefRef[i][i] = 1.;
412                 } else {
413                     corrCoefRef[i][j] = i + j + 1;
414                 }
415             }
416         }
417 
418         // Reference covariance matrix
419         final double[][] Pref = new double[6][6];
420         for (int i = 0; i < 6; i++) {
421             for (int j = 0; j < 6; j++) {
422                 Pref[i][j] = corrCoefRef[i][j]*sigmaPV[i]*sigmaPV[j];
423             }
424         }
425 
426         // Reference propagator numbers
427         final ObservableSatellite[] sats = {
428             new ObservableSatellite(0),
429             new ObservableSatellite(2)
430         };
431 
432         // Create PV measurements
433         final PV[] pvs = new PV[2];
434         pvs[0] = new PV(date, position, velocity, Pref, baseWeight, sats[0]);
435         pvs[1] = new PV(date, position, velocity, Pref, baseWeight, sats[1]);
436 
437         // Tolerance
438         final double eps = 1.8e-15; // tolerance
439 
440         // Check data
441         for (int k = 0; k < pvs.length; k++) {
442             final PV pv = pvs[k];
443 
444             // Propagator numbers
445             Assertions.assertEquals(sats[k].getPropagatorIndex(), pv.getSatellites().get(0).getPropagatorIndex());
446 
447             // Weights
448             for (int i = 0; i < 6; i++) {
449                 Assertions.assertEquals(baseWeight, pv.getBaseWeight()[i], eps);
450             }
451             // Sigmas
452             for (int i = 0; i < 6; i++) {
453                 Assertions.assertEquals(sigmaPV[i], pv.getTheoreticalStandardDeviation()[i]  , eps);
454             }
455             // Covariances
456             final double[][] P = pv.getCovarianceMatrix();
457             // Substract with ref and get the norm
458             final double normP = MatrixUtils.createRealMatrix(P).subtract(MatrixUtils.createRealMatrix(Pref)).getNorm1();
459             Assertions.assertEquals(0., normP, eps);
460 
461             // Correlation coef
462             final double[][] corrCoef = pv.getCorrelationCoefficientsMatrix();
463             // Substract with ref and get the norm
464             final double normCorrCoef = MatrixUtils.createRealMatrix(corrCoef).subtract(MatrixUtils.createRealMatrix(corrCoefRef)).getNorm1();
465             Assertions.assertEquals(0., normCorrCoef, eps);
466         }
467 
468     }
469 
470     /** Test exceptions raised if the covariance matrix does not have the proper size. */
471     @Test
472     public void testExceptions() {
473         // Context
474         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
475 
476         // Dummy P, V, T
477         final Vector3D     position = context.initialOrbit.getPosition();
478         final Vector3D     velocity = context.initialOrbit.getPVCoordinates().getVelocity();
479         final AbsoluteDate date     = context.initialOrbit.getDate();
480         final double       weight   = 1.;
481 
482         // Build with two 3-sized vectors
483         try {
484             new PV(date, position, velocity, new double[] {0., 0., 0.}, new double[] {1.}, weight, new ObservableSatellite(0));
485             Assertions.fail("An OrekitException should have been thrown");
486         } catch (OrekitException e) {
487             // An exception should indeed be raised here
488         }
489 
490         // Build with one 6-sized vector
491         try {
492             new PV(date, position, velocity, new double[] {0., 0., 0.}, weight, new ObservableSatellite(0));
493             Assertions.fail("An OrekitException should have been thrown");
494         } catch (OrekitException e) {
495             // An exception should indeed be raised here
496         }
497 
498         // Build with two 3x3 matrices
499         try {
500             new PV(date, position, velocity, new double[][] {{0., 0.}, {0., 0.}},
501                    new double[][] {{0., 0.}, {0., 0.}}, weight, new ObservableSatellite(0));
502             Assertions.fail("An OrekitException should have been thrown");
503         } catch (OrekitException e) {
504             // An exception should indeed be raised here
505         }
506 
507         // Build with one 6x6 matrix
508         try {
509             new PV(date, position, velocity, new double[][] {{0., 0.}, {0., 0.}}, weight, new ObservableSatellite(0));
510             Assertions.fail("An OrekitException should have been thrown");
511         } catch (OrekitException e) {
512             // An exception should indeed be raised here
513         }
514     }
515 }
516 
517