1   /* Copyright 2002-2026 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.sequential;
18  
19  import org.hipparchus.filtering.kalman.ProcessEstimate;
20  import org.hipparchus.filtering.kalman.unscented.UnscentedEvolution;
21  import org.hipparchus.filtering.kalman.unscented.UnscentedProcess;
22  import org.hipparchus.linear.ArrayRealVector;
23  import org.hipparchus.linear.MatrixUtils;
24  import org.hipparchus.linear.RealMatrix;
25  import org.hipparchus.linear.RealVector;
26  import org.orekit.estimation.measurements.EstimatedMeasurement;
27  import org.orekit.estimation.measurements.EstimatedMeasurementBase;
28  import org.orekit.estimation.measurements.ObservedMeasurement;
29  import org.orekit.orbits.CartesianOrbit;
30  import org.orekit.orbits.Orbit;
31  import org.orekit.propagation.Propagator;
32  import org.orekit.propagation.SpacecraftState;
33  import org.orekit.propagation.conversion.AbstractPropagatorBuilder;
34  import org.orekit.propagation.conversion.PropagatorBuilder;
35  import org.orekit.time.AbsoluteDate;
36  import org.orekit.utils.ParameterDriver;
37  import org.orekit.utils.ParameterDriversList;
38  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
39  
40  import java.util.List;
41  
42  /** Class defining the process model dynamics to use with a {@link UnscentedKalmanEstimator}.
43   * @author Gaƫtan Pierre
44   * @author Bryan Cazabonne
45   * @since 11.3
46   */
47  public class UnscentedKalmanModel extends AbstractKalmanEstimationCommon implements UnscentedProcess<MeasurementDecorator> {
48  
49      /** Reference values. */
50      private final double[] referenceValues;
51  
52      /** Unscented Kalman process model constructor (package private).
53       * @param propagatorBuilders propagators builders used to evaluate the orbits.
54       * @param covarianceMatricesProviders provider for covariance matrix
55       * @param estimatedMeasurementParameters measurement parameters to estimate
56       * @param measurementProcessNoiseMatrix provider for measurement process noise matrix
57       */
58      protected UnscentedKalmanModel(final List<PropagatorBuilder> propagatorBuilders,
59                                     final List<CovarianceMatrixProvider> covarianceMatricesProviders,
60                                     final ParameterDriversList estimatedMeasurementParameters,
61                                     final CovarianceMatrixProvider measurementProcessNoiseMatrix) {
62  
63          super(propagatorBuilders, covarianceMatricesProviders, estimatedMeasurementParameters, measurementProcessNoiseMatrix);
64  
65          // Record the initial reference values
66          int stateDimension = 0;
67          for (final ParameterDriver ignored : getEstimatedOrbitalParameters().getDrivers()) {
68              stateDimension += 1;
69          }
70          for (final ParameterDriver ignored : getEstimatedPropagationParameters().getDrivers()) {
71              stateDimension += 1;
72          }
73          for (final ParameterDriver ignored : getEstimatedMeasurementsParameters().getDrivers()) {
74              stateDimension += 1;
75          }
76  
77          this.referenceValues = new double[stateDimension];
78          int index = 0;
79          for (final ParameterDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
80              referenceValues[index++] = driver.getReferenceValue();
81          }
82          for (final ParameterDriver driver : getEstimatedPropagationParameters().getDrivers()) {
83              referenceValues[index++] = driver.getReferenceValue();
84          }
85          for (final ParameterDriver driver : getEstimatedMeasurementsParameters().getDrivers()) {
86              referenceValues[index++] = driver.getReferenceValue();
87          }
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      public UnscentedEvolution getEvolution(final double previousTime, final RealVector[] sigmaPoints,
93                                             final MeasurementDecorator measurement) {
94  
95          // Set a reference date for all measurements parameters that lack one (including the not estimated ones)
96          final ObservedMeasurement<?> observedMeasurement = measurement.getObservedMeasurement();
97          for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
98              if (driver.getReferenceDate() == null) {
99                  driver.setReferenceDate(getBuilders().getFirst().getOrbitalParameterFactory().getDate());
100             }
101         }
102 
103         // Increment measurement number
104         incrementCurrentMeasurementNumber();
105 
106         // Update the current date
107         setCurrentDate(measurement.getObservedMeasurement().getDate());
108 
109         // Initialize array of predicted sigma points
110         final RealVector[] predictedSigmaPoints = new RealVector[sigmaPoints.length];
111 
112         // Propagate each sigma point
113         //
114         // We need to make a choice about what happens with the non-estimated parts of the orbital states.
115         // Here we've assumed that the zero'th sigma point is roughly the mean and keep those propagated
116         // orbital parameters.  This is why we loop backward through the sigma-points and don't reset the
117         // propagator builders on the last iteration (corresponding to the zero-th sigma point).
118         //
119         // Note that -not- resetting the builders on the last iteration means that their time-stamps correspond
120         // to the prediction time.  The assumption is that the unscented filter calls getEvolution, then
121         // getPredictedMeasurements, then getInnovation.
122         for (int i = sigmaPoints.length - 1; i >= 0; i--) {
123 
124             // Set parameters for this sigma point
125             final RealVector sigmaPoint = sigmaPoints[i].copy();
126             updateParameters(sigmaPoint);
127 
128             // Get propagators
129             final Propagator[] propagators = getEstimatedPropagators();
130 
131             // Do prediction
132             predictedSigmaPoints[i] =
133                     predictState(observedMeasurement.getDate(), sigmaPoint, propagators, i != 0);
134         }
135 
136         // Reset the driver reference values based on the first sigma point
137         int d = 0;
138         for (final DelegatingDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
139             driver.setReferenceValue(referenceValues[d]);
140             driver.setNormalizedValue(predictedSigmaPoints[0].getEntry(d));
141             referenceValues[d] = driver.getValue();
142 
143             // Make remaining sigma points relative to the first
144             for (int i = 1; i < predictedSigmaPoints.length; ++i) {
145                 predictedSigmaPoints[i].setEntry(d, predictedSigmaPoints[i].getEntry(d) - predictedSigmaPoints[0].getEntry(d));
146             }
147             predictedSigmaPoints[0].setEntry(d, 0.0);
148 
149             d += 1;
150         }
151 
152         // Return
153         return new UnscentedEvolution(measurement.getTime(), predictedSigmaPoints);
154     }
155 
156     /** {@inheritDoc} */
157     @Override
158     public RealMatrix getProcessNoiseMatrix(final double previousTime, final RealVector predictedState,
159                                             final MeasurementDecorator measurement) {
160         // Set parameters from predicted state
161         final RealVector predictedStateCopy = predictedState.copy();
162         updateParameters(predictedStateCopy);
163 
164         // Get propagators
165         Propagator[] propagators = getEstimatedPropagators();
166 
167         // "updateParameters" sets the correct orbital info, but doesn't reset the time.
168         for (int k = 0; k < propagators.length; ++k) {
169             final SpacecraftState predicted = propagators[k].getInitialState();
170             final Orbit predictedOrbit = new CartesianOrbit(predicted.getPVCoordinates(),
171                                                             predicted.getFrame(),
172                                                             measurement.getObservedMeasurement().getDate(),
173                                                             predicted.getOrbit().getMu());
174             getBuilders().get(k).resetOrbit(predictedOrbit);
175         }
176         propagators = getEstimatedPropagators();
177 
178         // Predicted states
179         for (int k = 0; k < propagators.length; ++k) {
180             setPredictedSpacecraftState(propagators[k].getInitialState(), k);
181         }
182 
183         return getNormalizedProcessNoise(predictedState.getDimension());
184     }
185 
186     /** {@inheritDoc} */
187     @Override
188     public RealVector[] getPredictedMeasurements(final RealVector[] predictedSigmaPoints, final MeasurementDecorator measurement) {
189 
190         // Observed measurement
191         final ObservedMeasurement<?> observedMeasurement = measurement.getObservedMeasurement();
192 
193         // Standard deviation as a vector
194         final RealVector theoreticalStandardDeviation =
195                 MatrixUtils.createRealVector(observedMeasurement.getTheoreticalStandardDeviation());
196 
197         // Initialize arrays of predicted states and measurements
198         final RealVector[] predictedMeasurements = new RealVector[predictedSigmaPoints.length];
199 
200         // Loop on sigma points to predict measurements
201         for (int i = 0; i < predictedSigmaPoints.length; ++i) {
202             // Set parameters for this sigma point
203             final RealVector predictedSigmaPoint = predictedSigmaPoints[i].copy();
204             updateParameters(predictedSigmaPoint);
205 
206             // Get propagators
207             final Propagator[] propagators = getEstimatedPropagators();
208 
209             // Predicted states
210             final SpacecraftState[] predictedStates = new SpacecraftState[propagators.length];
211             for (int k = 0; k < propagators.length; ++k) {
212                 predictedStates[k] = propagators[k].getInitialState();
213             }
214 
215             // Calculated estimated measurement from predicted sigma point
216             final EstimatedMeasurement<?> estimated = estimateMeasurement(observedMeasurement, getCurrentMeasurementNumber(),
217                                                                                    KalmanEstimatorUtil.filterRelevant(observedMeasurement,
218                                                                                                                       predictedStates));
219             predictedMeasurements[i] = new ArrayRealVector(estimated.getEstimatedValue())
220                     .ebeDivide(theoreticalStandardDeviation);
221         }
222 
223         // Return the predicted measurements
224         return predictedMeasurements;
225 
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public RealVector getInnovation(final MeasurementDecorator measurement, final RealVector predictedMeas,
231                                     final RealVector predictedState, final RealMatrix innovationCovarianceMatrix) {
232         // Standard deviation as a vector
233         final RealVector theoreticalStandardDeviation =
234                 MatrixUtils.createRealVector(measurement.getObservedMeasurement().getTheoreticalStandardDeviation());
235 
236         // Get propagators
237         final Propagator[] propagators = getEstimatedPropagators();
238 
239         // Predicted states
240         for (int k = 0; k < propagators.length; ++k) {
241             setPredictedSpacecraftState(propagators[k].getInitialState(), k);
242         }
243 
244         // set estimated value to the predicted value from the filter
245         final EstimatedMeasurement<?> predictedMeasurement =
246             estimateMeasurement(measurement.getObservedMeasurement(), getCurrentMeasurementNumber(),
247                                 KalmanEstimatorUtil.filterRelevant(measurement.getObservedMeasurement(),
248                                 getPredictedSpacecraftStates()));
249         setPredictedMeasurement(predictedMeasurement);
250         predictedMeasurement.setEstimatedValue(predictedMeas.ebeMultiply(theoreticalStandardDeviation).toArray());
251 
252         // Check for outliers
253         KalmanEstimatorUtil.applyDynamicOutlierFilter(predictedMeasurement, innovationCovarianceMatrix);
254 
255         // Compute the innovation vector
256         return KalmanEstimatorUtil.computeInnovationVector(predictedMeasurement,
257                 predictedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
258     }
259 
260 
261     private RealVector predictState(final AbsoluteDate date,
262                                     final RealVector previousState,
263                                     final Propagator[] propagators,
264                                     final boolean resetState) {
265 
266         // Initialise predicted state
267         final RealVector predictedState = previousState.copy();
268 
269         // Orbital parameters counter
270         int jOrb = 0;
271 
272         // Loop over propagators
273         for (int k = 0; k < propagators.length; ++k) {
274 
275             // Record original state
276             final SpacecraftState originalState = propagators[k].getInitialState();
277 
278             // Propagate
279             final SpacecraftState predicted = propagators[k].propagate(date);
280 
281             // Update the builder with the predicted orbit
282             // This updates the orbital drivers with the values of the predicted orbit
283             getBuilders().get(k).resetOrbit(predicted.getOrbit());
284 
285             // Additionally, for PropagatorBuilders which use mass, update the builder with the predicted mass value.
286             // If any mass changes have occurred during this estimation step, such as maneuvers,
287             // the updated mass value must be carried over so that new Propagators from this builder start with the updated mass.
288             if (getBuilders().get(k) instanceof AbstractPropagatorBuilder) {
289                 ((AbstractPropagatorBuilder<?, ?, ?>) (getBuilders().get(k))).setMass(predicted.getMass());
290             }
291 
292             // The orbital parameters in the state vector are replaced with their predicted values
293             // The propagation & measurement parameters are not changed by the prediction (i.e. the propagation)
294             // As the propagator builder was previously updated with the predicted orbit,
295             // the selected orbital drivers are already up to date with the prediction
296             final ParameterDriversList drivers = getBuilders().
297                                                  get(k).
298                                                  getOrbitalParameterFactory().
299                                                  getOrbitalParametersDrivers();
300             for (DelegatingDriver orbitalDriver : drivers.getDrivers()) {
301                 if (orbitalDriver.isSelected()) {
302                     orbitalDriver.setReferenceValue(referenceValues[jOrb]);
303                     predictedState.setEntry(jOrb, orbitalDriver.getNormalizedValue());
304 
305                     jOrb += 1;
306                 }
307             }
308 
309             // Set the builder back to the original time
310             if (resetState) {
311                 getBuilders().get(k).resetOrbit(originalState.getOrbit());
312             }
313         }
314 
315         return predictedState;
316     }
317 
318 
319     /** Finalize estimation.
320      * @param observedMeasurement measurement that has just been processed
321      * @param estimate corrected estimate
322      */
323     public void finalizeEstimation(final ObservedMeasurement<?> observedMeasurement,
324                                    final ProcessEstimate estimate) {
325         // Update the parameters with the estimated state
326         // The min/max values of the parameters are handled by the ParameterDriver implementation
327         setCorrectedEstimate(estimate);
328         updateParameters(estimate.getState());
329 
330         // Get the estimated propagator (mirroring parameter update in the builder)
331         // and the estimated spacecraft state
332         final Propagator[] estimatedPropagators = getEstimatedPropagators();
333         for (int k = 0; k < estimatedPropagators.length; ++k) {
334             setCorrectedSpacecraftState(estimatedPropagators[k].getInitialState(), k);
335         }
336 
337         // Corrected measurement
338         setCorrectedMeasurement(estimateMeasurement(observedMeasurement, getCurrentMeasurementNumber(),
339                                                     KalmanEstimatorUtil.filterRelevant(observedMeasurement,
340                                                     getCorrectedSpacecraftStates())));
341     }
342 
343     /**
344      * Estimate measurement (without derivatives).
345      * @param <T> measurement type
346      * @param observedMeasurement observed measurement
347      * @param measurementNumber measurement number
348      * @param spacecraftStates states
349      * @return estimated measurement
350      * @since 12.1
351      */
352     private static <T extends ObservedMeasurement<T>> EstimatedMeasurement<T> estimateMeasurement(final ObservedMeasurement<T> observedMeasurement,
353                                                                                                   final int measurementNumber,
354                                                                                                   final SpacecraftState[] spacecraftStates) {
355         final EstimatedMeasurementBase<T> estimatedMeasurementBase = observedMeasurement.
356                 estimateWithoutDerivatives(measurementNumber, measurementNumber,
357                 KalmanEstimatorUtil.filterRelevant(observedMeasurement, spacecraftStates));
358         return new EstimatedMeasurement<>(estimatedMeasurementBase);
359     }
360 
361     /** Update parameter drivers with a normalised state, adjusting state according to the driver limits.
362      * @param normalizedState the input state
363      * The min/max allowed values are handled by the parameter themselves.
364      */
365     private void updateParameters(final RealVector normalizedState) {
366         int i = 0;
367         for (final DelegatingDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
368             // let the parameter handle min/max clipping
369             driver.setReferenceValue(referenceValues[i]);
370             driver.setNormalizedValue(normalizedState.getEntry(i));
371             normalizedState.setEntry(i++, driver.getNormalizedValue());
372         }
373         for (final DelegatingDriver driver : getEstimatedPropagationParameters().getDrivers()) {
374             // let the parameter handle min/max clipping
375             driver.setNormalizedValue(normalizedState.getEntry(i));
376             normalizedState.setEntry(i++, driver.getNormalizedValue());
377         }
378         for (final DelegatingDriver driver : getEstimatedMeasurementsParameters().getDrivers()) {
379             // let the parameter handle min/max clipping
380             driver.setNormalizedValue(normalizedState.getEntry(i));
381             normalizedState.setEntry(i++, driver.getNormalizedValue());
382         }
383     }
384 }