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 PositionTest {
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 PositionMeasurementCreator(),
59                                                                 1.0, 3.0, 300.0);
60  
61          propagator.clearStepHandlers();
62  
63          // Prepare statistics for position values difference
64          final StreamingStatistics[] pvDiffStat = new StreamingStatistics[3];
65          for (int i = 0; i < 3; 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 position 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 < 3; 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.8e-7);
88              Assertions.assertEquals(0.0, pvDiffStat[i].getStandardDeviation(), 2.3e-7);
89          }
90  
91          // Test measurement type
92          Assertions.assertEquals(Position.MEASUREMENT_TYPE, measurements.get(0).getMeasurementType());
93      }
94  
95      /** Test the values of the state derivatives using a numerical.
96       * finite differences calculation as a reference
97       */
98      @Test
99      public void testStateDerivatives() {
100 
101         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
102 
103         final NumericalPropagatorBuilder propagatorBuilder =
104                         context.createBuilder(OrbitType.KEPLERIAN, PositionAngleType.TRUE, true,
105                                               1.0e-6, 60.0, 0.001);
106 
107         // create perfect range measurements
108         final Propagator propagator = EstimationTestUtils.createPropagator(context.initialOrbit,
109                                                                            propagatorBuilder);
110         final List<ObservedMeasurement<?>> measurements =
111                         EstimationTestUtils.createMeasurements(propagator,
112                                                                new PositionMeasurementCreator(),
113                                                                1.0, 3.0, 300.0);
114         propagator.clearStepHandlers();
115 
116         double[] errorsP = new double[3 * 6 * measurements.size()];
117         int indexP = 0;
118         for (final ObservedMeasurement<?> measurement : measurements) {
119 
120             final AbsoluteDate    date      = measurement.getDate();
121             final SpacecraftState state     = propagator.propagate(date);
122             final double[][]      jacobian  = measurement.estimate(0, 0, new SpacecraftState[] { state }).getStateDerivatives(0);
123 
124             // compute a reference value using finite differences
125             final double[][] finiteDifferencesJacobian =
126                 Differentiation.differentiate(state1 ->
127                                                   measurement.
128                                                       estimateWithoutDerivatives(new SpacecraftState[] { state1 }).
129                                                       getEstimatedValue(),
130                                               measurement.getDimension(),
131                                               propagator.getAttitudeProvider(), OrbitType.CARTESIAN,
132                                               PositionAngleType.TRUE, 1.0, 3).value(state);
133 
134             Assertions.assertEquals(finiteDifferencesJacobian.length, jacobian.length);
135             Assertions.assertEquals(finiteDifferencesJacobian[0].length, jacobian[0].length);
136             for (int i = 0; i < jacobian.length; ++i) {
137                 for (int j = 0; j < jacobian[i].length; ++j) {
138                     final double relativeError = FastMath.abs((finiteDifferencesJacobian[i][j] - jacobian[i][j]) /
139                                                               finiteDifferencesJacobian[i][j]);
140                     errorsP[indexP++] = relativeError;
141                 }
142             }
143 
144         }
145 
146         // median errors
147         Assertions.assertEquals(0.0, new Median().evaluate(errorsP), 2.1e-100);
148 
149     }
150 
151     /** Test the position constructor with standard deviations for position given as one double.
152      */
153     @Test
154     public void testPositionWithSingleStandardDeviations() {
155 
156         // Context
157         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
158 
159         // Dummy P, T
160         final Vector3D     position = context.initialOrbit.getPosition();
161         final AbsoluteDate date     = context.initialOrbit.getDate();
162 
163         // Initialize standard deviations and weight
164         final double sigmaP     = 10.;
165         final double baseWeight = 0.5;
166 
167         // Reference covariance matrix and correlation coefficients
168         final double[][] Pref = new double[3][3];
169         for (int i = 0; i < 3; i++) {
170             Pref[i][i]     = FastMath.pow(sigmaP, 2);
171         }
172         final double[][] corrCoefRef = MatrixUtils.createRealIdentityMatrix(3).getData();
173 
174         // Reference propagator numbers
175         final ObservableSatellite[] sats = {
176             new ObservableSatellite(0),
177             new ObservableSatellite(2)
178         };
179 
180         // Create PV measurements
181         final Position[] ps = new Position[2];
182         ps[0] = new Position(date, position, sigmaP, baseWeight, sats[0]);
183         ps[1] = new Position(date, position, sigmaP, baseWeight, sats[1]);
184 
185         // Tolerance
186         final double eps = 1e-20; // tolerance
187 
188         // Check data
189         for (int k = 0; k < ps.length; k++) {
190             final Position p = ps[k];
191 
192             // Propagator numbers
193             Assertions.assertEquals(sats[k].getPropagatorIndex(), p.getSatellites().get(0).getPropagatorIndex());
194 
195             // Weights
196             for (int i = 0; i < 3; i++) {
197                 Assertions.assertEquals(baseWeight, p.getBaseWeight()[i], eps);
198             }
199             // Sigmas
200             for (int i = 0; i < 3; i++) {
201                 Assertions.assertEquals(sigmaP, p.getTheoreticalStandardDeviation()[i]  , eps);
202             }
203             // Covariances
204             final double[][] P = p.getCovarianceMatrix();
205             // Substract with ref and get the norm
206             final double normP = MatrixUtils.createRealMatrix(P).subtract(MatrixUtils.createRealMatrix(Pref)).getNorm1();
207             Assertions.assertEquals(0., normP, eps);
208 
209             // Correlation coef
210             final double[][] corrCoef = p.getCorrelationCoefficientsMatrix();
211             // Substract with ref and get the norm
212             final double normCorrCoef = MatrixUtils.createRealMatrix(corrCoef).subtract(MatrixUtils.createRealMatrix(corrCoefRef)).getNorm1();
213             Assertions.assertEquals(0., normCorrCoef, eps);
214         }
215     }
216 
217     /** Test the Position constructor with standard deviations for position given as a 3-sized vector.
218      */
219     @Test
220     public void testPositionWithVectorStandardDeviations() {
221 
222         // Context
223         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
224 
225         // Dummy P, T
226         final Vector3D     position = context.initialOrbit.getPosition();
227         final AbsoluteDate date     = context.initialOrbit.getDate();
228 
229         // Initialize standard deviations and weight
230         final double[] sigmaP  = {10., 20., 30.};
231         final double baseWeight = 0.5;
232 
233         // Reference covariance matrix and correlation coefficients
234         final double[][] Pref = new double[3][3];
235         for (int i = 0; i < 3; i++) {
236             Pref[i][i]     = FastMath.pow(sigmaP[i], 2);
237         }
238         final double[][] corrCoefRef = MatrixUtils.createRealIdentityMatrix(3).getData();
239 
240         // Reference propagator numbers
241         final ObservableSatellite[] sats = {
242             new ObservableSatellite(0),
243             new ObservableSatellite(2)
244         };
245 
246         // Create PV measurements
247         final Position[] ps = new Position[2];
248         ps[0] = new Position(date, position, sigmaP, baseWeight, sats[0]);
249         ps[1] = new Position(date, position, sigmaP, baseWeight, sats[1]);
250 
251         // Tolerance
252         final double eps = 1e-20; // tolerance
253 
254         // Check data
255         for (int k = 0; k < ps.length; k++) {
256             final Position p = ps[k];
257 
258             // Propagator numbers
259             Assertions.assertEquals(sats[k].getPropagatorIndex(), p.getSatellites().get(0).getPropagatorIndex());
260 
261             // Weights
262             for (int i = 0; i < 3; i++) {
263                 Assertions.assertEquals(baseWeight, p.getBaseWeight()[i], eps);
264             }
265             // Sigmas
266             for (int i = 0; i < 3; i++) {
267                 Assertions.assertEquals(sigmaP[i], p.getTheoreticalStandardDeviation()[i]  , eps);
268             }
269             // Covariances
270             final double[][] P = p.getCovarianceMatrix();
271             // Substract with ref and get the norm
272             final double normP = MatrixUtils.createRealMatrix(P).subtract(MatrixUtils.createRealMatrix(Pref)).getNorm1();
273             Assertions.assertEquals(0., normP, eps);
274 
275             // Correlation coef
276             final double[][] corrCoef = p.getCorrelationCoefficientsMatrix();
277             // Substract with ref and get the norm
278             final double normCorrCoef = MatrixUtils.createRealMatrix(corrCoef).subtract(MatrixUtils.createRealMatrix(corrCoefRef)).getNorm1();
279             Assertions.assertEquals(0., normCorrCoef, eps);
280         }
281     }
282 
283     /** Test the Position constructor with 3x3 covariance matrix as input.
284      */
285     @Test
286     public void testPositionWithCovarianceMatrix() {
287         // Context
288         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
289 
290         // Dummy P, T
291         final Vector3D     position = context.initialOrbit.getPosition();
292         final AbsoluteDate date     = context.initialOrbit.getDate();
293 
294         // Initialize standard deviations and weight
295         final double[][] positionP = {{100., 400., 1200.}, {400., 400., 1800.}, {1200., 1800., 900.}};
296         final double baseWeight = 0.5;
297 
298         // Reference covariance matrix and correlation coefficients
299         final double[][] Pref = new double[3][3];
300         for (int i = 0; i < 3; i++) {
301             for (int j = i; j < 3; j++) {
302                 Pref[i][j]     = positionP[i][j];
303                 Pref[j][i]     = positionP[i][j];
304             }
305         }
306         final double[][] corrCoefRef3 = {{1., 2., 4.}, {2., 1., 3.}, {4., 3., 1.}};
307         final double[][] corrCoefRef  = new double[3][3];
308         for (int i = 0; i < 3; i++) {
309             for (int j = i; j < 3; j++) {
310                 corrCoefRef[i][j]     = corrCoefRef3[i][j];
311                 corrCoefRef[j][i]     = corrCoefRef3[i][j];
312             }
313         }
314 
315         // Reference propagator numbers
316         final ObservableSatellite[] sats = {
317             new ObservableSatellite(0),
318             new ObservableSatellite(2)
319         };
320 
321         // Reference standard deviations
322         final double[] sigmaP = {10., 20., 30.};
323 
324         // Create Position measurements
325         final Position[] ps = new Position[2];
326         ps[0] = new Position(date, position, positionP, baseWeight, sats[0]);
327         ps[1] = new Position(date, position, positionP, baseWeight, sats[1]);
328 
329         // Tolerance
330         final double eps = 6.7e-16; // tolerance
331 
332         // Check data
333         for (int k = 0; k < ps.length; k++) {
334             final Position p = ps[k];
335 
336             // Propagator numbers
337             Assertions.assertEquals(sats[k].getPropagatorIndex(), p.getSatellites().get(0).getPropagatorIndex());
338 
339             // Weights
340             for (int i = 0; i < 3; i++) {
341                 Assertions.assertEquals(baseWeight, p.getBaseWeight()[i], eps);
342             }
343             // Sigmas
344             for (int i = 0; i < 3; i++) {
345                 Assertions.assertEquals(sigmaP[i], p.getTheoreticalStandardDeviation()[i]  , eps);
346             }
347             // Covariances
348             final double[][] P = p.getCovarianceMatrix();
349             // Substract with ref and get the norm
350             final double normP = MatrixUtils.createRealMatrix(P).subtract(MatrixUtils.createRealMatrix(Pref)).getNorm1();
351             Assertions.assertEquals(0., normP, eps);
352 
353             // Correlation coef
354             final double[][] corrCoef = p.getCorrelationCoefficientsMatrix();
355             // Substract with ref and get the norm
356             final double normCorrCoef = MatrixUtils.createRealMatrix(corrCoef).subtract(MatrixUtils.createRealMatrix(corrCoefRef)).getNorm1();
357             Assertions.assertEquals(0., normCorrCoef, eps);
358         }
359 
360     }
361 
362     /** Test exceptions raised if the covariance matrix does not have the proper size. */
363     @Test
364     public void testExceptions() {
365         // Context
366         Context context = EstimationTestUtils.eccentricContext("regular-data:potential:tides");
367 
368         // Dummy P, T
369         final Vector3D     position = context.initialOrbit.getPosition();
370         final AbsoluteDate date     = context.initialOrbit.getDate();
371         final double       weight   = 1.;
372 
373         // Build with one 3-sized vectors
374         try {
375             new Position(date, position, new double[] {1.}, weight, new ObservableSatellite(0));
376             Assertions.fail("An OrekitException should have been thrown");
377         } catch (OrekitException e) {
378             // An exception should indeed be raised here
379         }
380 
381         // Build with one 3x3 matrix
382         try {
383             new Position(date, position, new double[][] {{0., 0.}, {0., 0.}}, weight, new ObservableSatellite(0));
384             Assertions.fail("An OrekitException should have been thrown");
385         } catch (OrekitException e) {
386             // An exception should indeed be raised here
387         }
388     }
389 }
390 
391