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