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  
18  package org.orekit.estimation.sequential;
19  
20  import org.hipparchus.linear.MatrixUtils;
21  import org.hipparchus.linear.RealMatrix;
22  import org.hipparchus.linear.RealVector;
23  import org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.BeforeEach;
25  import org.junit.jupiter.api.Test;
26  import org.orekit.estimation.DSSTContext;
27  import org.orekit.estimation.DSSTEstimationTestUtils;
28  import org.orekit.estimation.DSSTForce;
29  import org.orekit.estimation.measurements.GroundStation;
30  import org.orekit.estimation.measurements.ObservableSatellite;
31  import org.orekit.estimation.measurements.Range;
32  import org.orekit.estimation.measurements.modifiers.Bias;
33  import org.orekit.forces.radiation.RadiationSensitive;
34  import org.orekit.orbits.Orbit;
35  import org.orekit.orbits.OrbitType;
36  import org.orekit.orbits.PositionAngleType;
37  import org.orekit.propagation.PropagationType;
38  import org.orekit.propagation.SpacecraftState;
39  import org.orekit.propagation.conversion.DSSTPropagatorBuilder;
40  import org.orekit.time.AbsoluteDate;
41  import org.orekit.utils.ParameterDriver;
42  import org.orekit.utils.ParameterDriversList;
43  
44  import java.util.ArrayList;
45  import java.util.List;
46  
47  public class SemiAnalyticalKalmanModelTest {
48  
49      /** Orbit type for propagation. */
50      private final OrbitType orbitType = OrbitType.EQUINOCTIAL;
51  
52      /** Position angle for propagation. */
53      private final PositionAngleType positionAngleType = PositionAngleType.MEAN;
54  
55      /** Initial orbit. */
56      private Orbit orbit0;
57  
58      /** Propagator builder. */
59      private DSSTPropagatorBuilder propagatorBuilder;
60  
61      /** Covariance matrix provider. */
62      private CovarianceMatrixProvider covMatrixProvider;
63  
64      /** Estimated measurement parameters list. */
65      private ParameterDriversList estimatedMeasurementsParameters;
66  
67      /** Kalman extended estimator containing models. */
68      private SemiAnalyticalKalmanEstimator kalman;
69  
70      /** Kalman observer. */
71      private ModelLogger modelLogger;
72  
73      /** State size. */
74      private int M;
75  
76      /** Range after t0. */
77      private Range range;
78  
79      /** Driver for satellite range bias. */
80      private ParameterDriver satRangeBiasDriver;
81  
82      /** Driver for SRP coefficient. */
83      private ParameterDriver srpCoefDriver;
84  
85      /** Tolerance for the test. */
86      private final double tol = 1e-16;
87  
88      @BeforeEach
89      public void setup() {
90  
91          // Create context
92          DSSTContext context = DSSTEstimationTestUtils.eccentricContext("regular-data:potential:tides");
93  
94          // Initial orbit and date
95          this.orbit0 = context.initialOrbit;
96          ObservableSatellite sat = new ObservableSatellite(0);
97  
98          // Create propagator builder
99          this.propagatorBuilder = context.createBuilder(PropagationType.MEAN, PropagationType.OSCULATING, true,
100                                                        1.0e-6, 60.0, 10., DSSTForce.SOLAR_RADIATION_PRESSURE);
101 
102         // Create PV at t0
103         final AbsoluteDate date0 = context.initialOrbit.getDate();
104 
105         // Create one 0m range measurement at t0 + 10s
106         final AbsoluteDate date  = date0.shiftedBy(10.);
107         final GroundStation station = context.stations.get(0);
108         this.range = new Range(station, true, date, 18616150., 10., 1., sat);
109         // Exact range value is 1.8616150246470984E7 m
110 
111         // Add sat range bias to PV and select it
112         final Bias<Range> satRangeBias = new Bias<Range>(new String[] {"sat range bias"},
113                                                          new double[] {100.},
114                                                          new double[] {10.},
115                                                          new double[] {0.},
116                                                          new double[] {100.});
117         this.satRangeBiasDriver = satRangeBias.getParametersDrivers().get(0);
118         satRangeBiasDriver.setSelected(true);
119         satRangeBiasDriver.setReferenceDate(date);
120         range.addModifier(satRangeBias);
121         for (ParameterDriver driver : range.getParametersDrivers()) {
122             driver.setReferenceDate(date);
123         }
124 
125         // Gather list of meas parameters (only sat range bias here)
126         this.estimatedMeasurementsParameters = new ParameterDriversList();
127         for (final ParameterDriver driver : range.getParametersDrivers()) {
128             if (driver.isSelected()) {
129                 estimatedMeasurementsParameters.add(driver);
130             }
131         }
132         // Select SRP coefficient
133         this.srpCoefDriver = propagatorBuilder.getPropagationParametersDrivers().
134                         findByName(RadiationSensitive.REFLECTION_COEFFICIENT);
135         srpCoefDriver.setReferenceDate(date);
136         srpCoefDriver.setSelected(true);
137 
138         // Create a covariance matrix using the scales of the estimated parameters
139         final double[] scales = getParametersScale(propagatorBuilder, estimatedMeasurementsParameters);
140         this.M = scales.length;
141         this.covMatrixProvider = setInitialCovarianceMatrix(scales);
142 
143         // Initialize Kalman
144         final SemiAnalyticalKalmanEstimatorBuilder kalmanBuilder = new SemiAnalyticalKalmanEstimatorBuilder();
145         kalmanBuilder.addPropagationConfiguration(propagatorBuilder, covMatrixProvider);
146         kalmanBuilder.estimatedMeasurementsParameters(estimatedMeasurementsParameters, null);
147         this.kalman = kalmanBuilder.build();
148         this.modelLogger = new ModelLogger();
149         kalman.setObserver(modelLogger);
150     }
151 
152     @Test
153     public void ModelPhysicalOutputsTest() {
154 
155         // Check model at t0 before any measurement is added
156         // -------------------------------------------------
157         checkModelAtT0();
158 
159     }
160 
161     /** Check the model physical outputs at t0 before any measurement is added. */
162     private void checkModelAtT0() {
163 
164         // Instantiate a Model from attributes
165         final SemiAnalyticalKalmanModel model = new SemiAnalyticalKalmanModel(propagatorBuilder,
166                                                   covMatrixProvider,
167                                                   estimatedMeasurementsParameters,
168                                                   null);
169         model.setObserver(modelLogger);
170 
171         // Evaluate at t0
172         // --------------
173 
174         // Observer
175         Assertions.assertNotNull(model.getObserver());
176 
177         // Time
178         Assertions.assertEquals(0., model.getEstimate().getTime(), 0.);
179         Assertions.assertEquals(0., model.getCurrentDate().durationFrom(orbit0.getDate()), 0.);
180 
181         // Measurement number
182         Assertions.assertEquals(0, model.getCurrentMeasurementNumber());
183 
184         // Normalized state - is zeros
185         final RealVector stateN = model.getEstimate().getState();
186         Assertions.assertArrayEquals(new double[M], stateN.toArray(), tol);
187 
188         // Physical state - = initialized
189         final RealVector x = model.getPhysicalEstimatedState();
190         final RealVector expX = MatrixUtils.createRealVector(M);
191         final double[] orbitState0 = new double[6];
192         orbitType.mapOrbitToArray(orbit0, positionAngleType, orbitState0, null);
193         expX.setSubVector(0, MatrixUtils.createRealVector(orbitState0));
194         expX.setEntry(6, srpCoefDriver.getReferenceValue());
195         expX.setEntry(7, satRangeBiasDriver.getReferenceValue());
196         final double[] dX = x.subtract(expX).toArray();
197         Assertions.assertArrayEquals(new double[M], dX, tol);
198 
199         // Normalized covariance - filled with 1
200         final double[][] Pn = model.getEstimate().getCovariance().getData();
201         final double[][] expPn = new double[M][M];
202         for (int i = 0; i < M; i++) {
203             for (int j = 0; j < M; j++) {
204                 expPn[i][j] = 1.;
205             }
206             Assertions.assertArrayEquals(expPn[i], Pn[i], tol, "Failed on line " + i);
207         }
208 
209         // Physical covariance = initialized
210         final RealMatrix P   = model.getPhysicalEstimatedCovarianceMatrix();
211         final RealMatrix expP = covMatrixProvider.getInitialCovarianceMatrix(new SpacecraftState(orbit0));
212         final double[][] dP = P.subtract(expP).getData();
213         for (int i = 0; i < M; i++) {
214             Assertions.assertArrayEquals(new double[M], dP[i], tol, "Failed on line " + i);
215         }
216 
217         // Check that other "physical" matrices are null
218         Assertions.assertNull(model.getEstimate().getInnovationCovariance());
219         Assertions.assertNull(model.getPhysicalInnovationCovarianceMatrix());
220         Assertions.assertNull(model.getEstimate().getKalmanGain());
221         Assertions.assertNull(model.getPhysicalKalmanGain());
222         Assertions.assertNull(model.getEstimate().getMeasurementJacobian());
223         Assertions.assertNull(model.getPhysicalMeasurementJacobian());
224         Assertions.assertNull(model.getEstimate().getStateTransitionMatrix());
225         Assertions.assertNull(model.getPhysicalStateTransitionMatrix());
226     }
227 
228     /** Get an array of the scales of the estimated parameters.
229      * @param builder propagator builder
230      * @param estimatedMeasurementsParameters estimated measurements parameters
231      * @return array containing the scales of the estimated parameter
232      */
233     private double[] getParametersScale(final DSSTPropagatorBuilder builder,
234                                         final ParameterDriversList estimatedMeasurementsParameters) {
235         final List<Double> scaleList = new ArrayList<>();
236 
237         // Orbital parameters
238         for (ParameterDriver driver : builder.getOrbitalParametersDrivers().getDrivers()) {
239             if (driver.isSelected()) {
240                 scaleList.add(driver.getScale());
241             }
242         }
243 
244         // Propagation parameters
245         for (ParameterDriver driver : builder.getPropagationParametersDrivers().getDrivers()) {
246             if (driver.isSelected()) {
247                 scaleList.add(driver.getScale());
248             }
249         }
250 
251         // Measurement parameters
252         for (ParameterDriver driver : estimatedMeasurementsParameters.getDrivers()) {
253             if (driver.isSelected()) {
254                 scaleList.add(driver.getScale());
255             }
256         }
257 
258         final double[] scales = new double[scaleList.size()];
259         for (int i = 0; i < scaleList.size(); i++) {
260             scales[i] = scaleList.get(i);
261         }
262         return scales;
263     }
264 
265     /** Create a covariance matrix provider with initial and process noise matrix constant and identical.
266      * Each element Pij of the initial covariance matrix equals:
267      * Pij = scales[i]*scales[j]
268      * @param scales scales of the estimated parameters
269      * @return covariance matrix provider
270      */
271     private CovarianceMatrixProvider setInitialCovarianceMatrix(final double[] scales) {
272 
273         final int n = scales.length;
274         final RealMatrix cov = MatrixUtils.createRealMatrix(n, n);
275         for (int i = 0; i < n; i++) {
276             for (int j = 0; j < n; j++) {
277                 cov.setEntry(i, j, scales[i] * scales[j]);
278             }
279         }
280         return new ConstantProcessNoise(cov);
281     }
282 
283 
284     /** Observer allowing to get Kalman model after a measurement was processed in the Kalman filter. */
285     public class ModelLogger implements KalmanObserver {
286         KalmanEstimation estimation;
287 
288         @Override
289         public void evaluationPerformed(KalmanEstimation estimation) {
290             this.estimation = estimation;
291         }
292     }
293 
294 }