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;
18  
19  import org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.linear.MatrixUtils;
21  import org.hipparchus.linear.RealMatrix;
22  import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer.Optimum;
23  import org.hipparchus.util.FastMath;
24  import org.junit.jupiter.api.Assertions;
25  import org.orekit.Utils;
26  import org.orekit.bodies.CelestialBodyFactory;
27  import org.orekit.bodies.OneAxisEllipsoid;
28  import org.orekit.data.DataContext;
29  import org.orekit.estimation.leastsquares.BatchLSEstimator;
30  import org.orekit.estimation.measurements.EstimatedMeasurement;
31  import org.orekit.estimation.measurements.MeasurementCreator;
32  import org.orekit.estimation.measurements.ObservedMeasurement;
33  import org.orekit.estimation.sequential.KalmanEstimator;
34  import org.orekit.forces.gravity.potential.AstronomicalAmplitudeReader;
35  import org.orekit.forces.gravity.potential.FESCHatEpsilonReader;
36  import org.orekit.forces.gravity.potential.GRGSFormatReader;
37  import org.orekit.forces.gravity.potential.GravityFieldFactory;
38  import org.orekit.forces.gravity.potential.OceanLoadDeformationCoefficients;
39  import org.orekit.frames.EOPHistory;
40  import org.orekit.frames.FramesFactory;
41  import org.orekit.models.earth.displacement.StationDisplacement;
42  import org.orekit.models.earth.displacement.TidalDisplacement;
43  import org.orekit.orbits.KeplerianOrbit;
44  import org.orekit.orbits.Orbit;
45  import org.orekit.orbits.PositionAngleType;
46  import org.orekit.propagation.Propagator;
47  import org.orekit.propagation.conversion.PropagatorBuilder;
48  import org.orekit.time.AbsoluteDate;
49  import org.orekit.time.TimeScalesFactory;
50  import org.orekit.utils.Constants;
51  import org.orekit.utils.IERSConventions;
52  import org.orekit.utils.ParameterDriver;
53  
54  import java.util.Arrays;
55  import java.util.List;
56  import java.util.Map;
57  
58  /** Utility class for orbit determination tests. */
59  public class KeplerianEstimationTestUtils {
60  
61      public static KeplerianContext eccentricContext(final String dataRoot) {
62  
63          Utils.setDataRoot(dataRoot);
64          KeplerianContext context = new KeplerianContext();
65          context.conventions = IERSConventions.IERS_2010;
66          context.earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
67                                               Constants.WGS84_EARTH_FLATTENING,
68                                               FramesFactory.getITRF(context.conventions, true));
69          context.sun = CelestialBodyFactory.getSun();
70          context.moon = CelestialBodyFactory.getMoon();
71          final EOPHistory eopHistory = FramesFactory.getEOPHistory(context.conventions, true);
72          context.utc = TimeScalesFactory.getUTC();
73          context.ut1 = TimeScalesFactory.getUT1(eopHistory);
74          context.displacements = new StationDisplacement[] {
75              new TidalDisplacement(Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
76                                    Constants.JPL_SSD_SUN_EARTH_PLUS_MOON_MASS_RATIO,
77                                    Constants.JPL_SSD_EARTH_MOON_MASS_RATIO,
78                                    context.sun, context.moon,
79                                    context.conventions, false)
80          };
81          GravityFieldFactory.addPotentialCoefficientsReader(new GRGSFormatReader("grim4s4_gr", true));
82          AstronomicalAmplitudeReader aaReader =
83                          new AstronomicalAmplitudeReader("hf-fes2004.dat", 5, 2, 3, 1.0);
84          DataContext.getDefault().getDataProvidersManager().feed(aaReader.getSupportedNames(), aaReader);
85          Map<Integer, Double> map = aaReader.getAstronomicalAmplitudesMap();
86          GravityFieldFactory.addOceanTidesReader(new FESCHatEpsilonReader("fes2004-7x7.dat",
87                                                                           0.01, FastMath.toRadians(1.0),
88                                                                           OceanLoadDeformationCoefficients.IERS_2010,
89                                                                           map));
90          context.initialOrbit = new KeplerianOrbit(15000000.0, 0.125, 1.25,
91                                                    0.250, 1.375, 0.0625, PositionAngleType.TRUE,
92                                                    FramesFactory.getEME2000(),
93                                                    new AbsoluteDate(2000, 2, 24, 11, 35, 47.0, context.utc),
94                                                    Constants.WGS84_EARTH_MU);
95  
96          context.stations = Arrays.asList(//context.createStation(-18.59146, -173.98363,   76.0, "Leimatu`a"),
97                                           context.createStation(-53.05388,  -75.01551, 1750.0, "Isla Desolación"),
98                                           context.createStation( 62.29639,   -7.01250,  880.0, "Slættaratindur")
99                                           //context.createStation( -4.01583,  103.12833, 3173.0, "Gunung Dempo")
100                         );
101 
102         return context;
103 
104     }
105 
106     public static Propagator createPropagator(final Orbit initialOrbit,
107                                               final PropagatorBuilder propagatorBuilder) {
108 
109         // override orbital parameters
110         double[] orbitArray = new double[6];
111         propagatorBuilder.getOrbitType().mapOrbitToArray(initialOrbit,
112                                                          propagatorBuilder.getPositionAngleType(),
113                                                          orbitArray, null);
114         for (int i = 0; i < orbitArray.length; ++i) {
115         	// here orbital paramaters drivers have only 1 estimated values on the all time period for orbit determination
116             propagatorBuilder.getOrbitalParametersDrivers().getDrivers().get(i).setValue(orbitArray[i], initialOrbit.getDate());
117         }
118 
119         return propagatorBuilder.buildPropagator();
120 
121     }
122 
123     public static List<ObservedMeasurement<?>> createMeasurements(final Propagator propagator,
124                                                                   final MeasurementCreator creator,
125                                                                   final double startPeriod, final double endPeriod,
126                                                                   final double step) {
127 
128         propagator.setStepHandler(step, creator);
129         final double       period = propagator.getInitialState().getOrbit().getKeplerianPeriod();
130         final AbsoluteDate start  = propagator.getInitialState().getDate().shiftedBy(startPeriod * period);
131         final AbsoluteDate end    = propagator.getInitialState().getDate().shiftedBy(endPeriod   * period);
132         propagator.propagate(start, end);
133 
134         final List<ObservedMeasurement<?>> measurements = creator.getMeasurements();
135 
136         for (final ObservedMeasurement<?> measurement : measurements) {
137             for (final ParameterDriver driver : measurement.getParametersDrivers()) {
138                 if (driver.getReferenceDate() == null) {
139                     driver.setReferenceDate(propagator.getInitialState().getDate());
140                 }
141             }
142         }
143 
144         return measurements;
145 
146     }
147 
148     /**
149      * Checker for batch LS estimator validation
150      * @param context Context used for the test
151      * @param estimator Batch LS estimator
152      * @param iterations Number of iterations expected
153      * @param evaluations Number of evaluations expected
154      * @param expectedRMS Expected RMS value
155      * @param rmsEps Tolerance on expected RMS
156      * @param expectedMax Expected weighted residual maximum
157      * @param maxEps Tolerance on weighted residual maximum
158      * @param expectedDeltaPos Expected position difference between estimated orbit and initial orbit
159      * @param posEps Tolerance on expected position difference
160      * @param expectedDeltaVel Expected velocity difference between estimated orbit and initial orbit
161      * @param velEps Tolerance on expected velocity difference
162      */
163     public static void checkFit(final KeplerianContext context, final BatchLSEstimator estimator,
164                                 final int iterations, final int evaluations,
165                                 final double expectedRMS,      final double rmsEps,
166                                 final double expectedMax,      final double maxEps,
167                                 final double expectedDeltaPos, final double posEps,
168                                 final double expectedDeltaVel, final double velEps) {
169 
170         final Orbit estimatedOrbit = estimator.estimate()[0].getInitialState().getOrbit();
171         final Vector3D estimatedPosition = estimatedOrbit.getPosition();
172         final Vector3D estimatedVelocity = estimatedOrbit.getPVCoordinates().getVelocity();
173 
174         Assertions.assertEquals(iterations, estimator.getIterationsCount());
175         Assertions.assertEquals(evaluations, estimator.getEvaluationsCount());
176         Optimum optimum = estimator.getOptimum();
177         Assertions.assertEquals(iterations, optimum.getIterations());
178         Assertions.assertEquals(evaluations, optimum.getEvaluations());
179 
180         int    k   = 0;
181         double sum = 0;
182         double max = 0;
183         for (final Map.Entry<ObservedMeasurement<?>, EstimatedMeasurement<?>> entry :
184              estimator.getLastEstimations().entrySet()) {
185             final ObservedMeasurement<?>  m = entry.getKey();
186             final EstimatedMeasurement<?> e = entry.getValue();
187             final double[]    weight      = m.getBaseWeight();
188             final double[]    sigma       = m.getTheoreticalStandardDeviation();
189             final double[]    observed    = m.getObservedValue();
190             final double[]    theoretical = e.getEstimatedValue();
191             for (int i = 0; i < m.getDimension(); ++i) {
192                 final double weightedResidual = weight[i] * (theoretical[i] - observed[i]) / sigma[i];
193                 ++k;
194                 sum += weightedResidual * weightedResidual;
195                 max = FastMath.max(max, FastMath.abs(weightedResidual));
196             }
197         }
198 
199         final double rms = FastMath.sqrt(sum / k);
200         final double deltaPos = Vector3D.distance(context.initialOrbit.getPosition(), estimatedPosition);
201         final double deltaVel = Vector3D.distance(context.initialOrbit.getPVCoordinates().getVelocity(), estimatedVelocity);
202         Assertions.assertEquals(expectedRMS,
203                             rms,
204                             rmsEps);
205         Assertions.assertEquals(expectedMax,
206                             max,
207                             maxEps);
208         Assertions.assertEquals(expectedDeltaPos,
209                             deltaPos,
210                             posEps);
211         Assertions.assertEquals(expectedDeltaVel,
212                             deltaVel,
213                             velEps);
214 
215     }
216 
217     /**
218      * Checker for Kalman estimator validation
219      * @param context context used for the test
220      * @param kalman Kalman filter
221      * @param measurements List of observed measurements to be processed by the Kalman
222      * @param refOrbit Reference orbits at last measurement date
223      * @param expectedDeltaPos Expected position difference between estimated orbit and reference orbit
224      * @param posEps Tolerance on expected position difference
225      * @param expectedDeltaVel Expected velocity difference between estimated orbit and reference orbit
226      * @param velEps Tolerance on expected velocity difference
227      * @param expectedSigmasPos Expected values for covariance matrix on position
228      * @param sigmaPosEps Tolerance on expected covariance matrix on position
229      * @param expectedSigmasVel Expected values for covariance matrix on velocity
230      * @param sigmaVelEps Tolerance on expected covariance matrix on velocity
231      */
232     public static void checkKalmanFit(final KeplerianContext context, final KalmanEstimator kalman,
233                                       final List<ObservedMeasurement<?>> measurements,
234                                       final Orbit refOrbit, final PositionAngleType positionAngleType,
235                                       final double expectedDeltaPos, final double posEps,
236                                       final double expectedDeltaVel, final double velEps,
237                                       final double[] expectedSigmasPos,final double sigmaPosEps,
238                                       final double[] expectedSigmasVel,final double sigmaVelEps)
239         {
240         checkKalmanFit(context, kalman, measurements,
241                        new Orbit[] { refOrbit },
242                        new PositionAngleType[] {positionAngleType},
243                        new double[] { expectedDeltaPos }, new double[] { posEps },
244                        new double[] { expectedDeltaVel }, new double[] { velEps },
245                        new double[][] { expectedSigmasPos }, new double[] { sigmaPosEps },
246                        new double[][] { expectedSigmasVel }, new double[] { sigmaVelEps });
247     }
248 
249     /**
250      * Checker for Kalman estimator validation
251      * @param context context used for the test
252      * @param kalman Kalman filter
253      * @param measurements List of observed measurements to be processed by the Kalman
254      * @param refOrbit Reference orbits at last measurement date
255      * @param expectedDeltaPos Expected position difference between estimated orbit and reference orbits
256      * @param posEps Tolerance on expected position difference
257      * @param expectedDeltaVel Expected velocity difference between estimated orbit and reference orbits
258      * @param velEps Tolerance on expected velocity difference
259      * @param expectedSigmasPos Expected values for covariance matrix on position
260      * @param sigmaPosEps Tolerance on expected covariance matrix on position
261      * @param expectedSigmasVel Expected values for covariance matrix on velocity
262      * @param sigmaVelEps Tolerance on expected covariance matrix on velocity
263      */
264     public static void checkKalmanFit(final KeplerianContext context, final KalmanEstimator kalman,
265                                       final List<ObservedMeasurement<?>> measurements,
266                                       final Orbit[] refOrbit, final PositionAngleType[] positionAngleType,
267                                       final double[] expectedDeltaPos, final double[] posEps,
268                                       final double[] expectedDeltaVel, final double []velEps,
269                                       final double[][] expectedSigmasPos,final double[] sigmaPosEps,
270                                       final double[][] expectedSigmasVel,final double[] sigmaVelEps)
271                                                       {
272 
273         // Add the measurements to the Kalman filter
274         Propagator[] estimated = kalman.processMeasurements(measurements);
275 
276         // Check the number of measurements processed by the filter
277         Assertions.assertEquals(measurements.size(), kalman.getCurrentMeasurementNumber());
278 
279         for (int k = 0; k < refOrbit.length; ++k) {
280             // Get the last estimation
281             final Orbit    estimatedOrbit    = estimated[k].getInitialState().getOrbit();
282             final Vector3D estimatedPosition = estimatedOrbit.getPosition();
283             final Vector3D estimatedVelocity = estimatedOrbit.getPVCoordinates().getVelocity();
284 
285             // Get the last covariance matrix estimation
286             final RealMatrix estimatedP = kalman.getPhysicalEstimatedCovarianceMatrix();
287 
288             // Convert the orbital part to Cartesian formalism
289             // Assuming all 6 orbital parameters are estimated by the filter
290             final double[][] dCdY = new double[6][6];
291             estimatedOrbit.getJacobianWrtParameters(positionAngleType[k], dCdY);
292             final RealMatrix Jacobian = MatrixUtils.createRealMatrix(dCdY);
293             final RealMatrix estimatedCartesianP =
294                             Jacobian.
295                             multiply(estimatedP.getSubMatrix(0, 5, 0, 5)).
296                             multiply(Jacobian.transpose());
297 
298             // Get the final sigmas (ie.sqrt of the diagonal of the Cartesian orbital covariance matrix)
299             final double[] sigmas = new double[6];
300             for (int i = 0; i < 6; i++) {
301                 sigmas[i] = FastMath.sqrt(estimatedCartesianP.getEntry(i, i));
302             }
303 
304             // Check the final orbit estimation & PV sigmas
305             final double deltaPosK = Vector3D.distance(refOrbit[k].getPosition(), estimatedPosition);
306             final double deltaVelK = Vector3D.distance(refOrbit[k].getPVCoordinates().getVelocity(), estimatedVelocity);
307             Assertions.assertEquals(expectedDeltaPos[k], deltaPosK, posEps[k]);
308             Assertions.assertEquals(expectedDeltaVel[k], deltaVelK, velEps[k]);
309 
310             for (int i = 0; i < 3; i++) {
311                 System.out.println(sigmas[i]);
312                 System.out.println(sigmas[i+3]);
313                 Assertions.assertEquals(expectedSigmasPos[k][i], sigmas[i],   sigmaPosEps[k]);
314                 Assertions.assertEquals(expectedSigmasVel[k][i], sigmas[i+3], sigmaVelEps[k]);
315             }
316         }
317     }
318 
319 }
320