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