UnscentedKalmanModel.java

  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.sequential;

  18. import org.hipparchus.filtering.kalman.ProcessEstimate;
  19. import org.hipparchus.filtering.kalman.unscented.UnscentedEvolution;
  20. import org.hipparchus.filtering.kalman.unscented.UnscentedProcess;
  21. import org.hipparchus.linear.ArrayRealVector;
  22. import org.hipparchus.linear.MatrixUtils;
  23. import org.hipparchus.linear.RealMatrix;
  24. import org.hipparchus.linear.RealVector;
  25. import org.orekit.estimation.measurements.EstimatedMeasurement;
  26. import org.orekit.estimation.measurements.EstimatedMeasurementBase;
  27. import org.orekit.estimation.measurements.ObservedMeasurement;
  28. import org.orekit.orbits.CartesianOrbit;
  29. import org.orekit.orbits.Orbit;
  30. import org.orekit.propagation.Propagator;
  31. import org.orekit.propagation.SpacecraftState;
  32. import org.orekit.propagation.conversion.AbstractPropagatorBuilder;
  33. import org.orekit.propagation.conversion.PropagatorBuilder;
  34. import org.orekit.time.AbsoluteDate;
  35. import org.orekit.utils.ParameterDriver;
  36. import org.orekit.utils.ParameterDriversList;
  37. import org.orekit.utils.ParameterDriversList.DelegatingDriver;

  38. import java.util.List;

  39. /** Class defining the process model dynamics to use with a {@link UnscentedKalmanEstimator}.
  40.  * @author GaĆ«tan Pierre
  41.  * @author Bryan Cazabonne
  42.  * @since 11.3
  43.  */
  44. public class UnscentedKalmanModel extends AbstractKalmanEstimationCommon implements UnscentedProcess<MeasurementDecorator> {

  45.     /** Reference values. */
  46.     private final double[] referenceValues;

  47.     /** Unscented Kalman process model constructor (package private).
  48.      * @param propagatorBuilders propagators builders used to evaluate the orbits.
  49.      * @param covarianceMatricesProviders provider for covariance matrix
  50.      * @param estimatedMeasurementParameters measurement parameters to estimate
  51.      * @param measurementProcessNoiseMatrix provider for measurement process noise matrix
  52.      */
  53.     protected UnscentedKalmanModel(final List<PropagatorBuilder> propagatorBuilders,
  54.                                    final List<CovarianceMatrixProvider> covarianceMatricesProviders,
  55.                                    final ParameterDriversList estimatedMeasurementParameters,
  56.                                    final CovarianceMatrixProvider measurementProcessNoiseMatrix) {

  57.         super(propagatorBuilders, covarianceMatricesProviders, estimatedMeasurementParameters, measurementProcessNoiseMatrix);

  58.         // Record the initial reference values
  59.         int stateDimension = 0;
  60.         for (final ParameterDriver ignored : getEstimatedOrbitalParameters().getDrivers()) {
  61.             stateDimension += 1;
  62.         }
  63.         for (final ParameterDriver ignored : getEstimatedPropagationParameters().getDrivers()) {
  64.             stateDimension += 1;
  65.         }
  66.         for (final ParameterDriver ignored : getEstimatedMeasurementsParameters().getDrivers()) {
  67.             stateDimension += 1;
  68.         }

  69.         this.referenceValues = new double[stateDimension];
  70.         int index = 0;
  71.         for (final ParameterDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
  72.             referenceValues[index++] = driver.getReferenceValue();
  73.         }
  74.         for (final ParameterDriver driver : getEstimatedPropagationParameters().getDrivers()) {
  75.             referenceValues[index++] = driver.getReferenceValue();
  76.         }
  77.         for (final ParameterDriver driver : getEstimatedMeasurementsParameters().getDrivers()) {
  78.             referenceValues[index++] = driver.getReferenceValue();
  79.         }
  80.     }

  81.     /** {@inheritDoc} */
  82.     @Override
  83.     public UnscentedEvolution getEvolution(final double previousTime, final RealVector[] sigmaPoints,
  84.                                            final MeasurementDecorator measurement) {

  85.         // Set a reference date for all measurements parameters that lack one (including the not estimated ones)
  86.         final ObservedMeasurement<?> observedMeasurement = measurement.getObservedMeasurement();
  87.         for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
  88.             if (driver.getReferenceDate() == null) {
  89.                 driver.setReferenceDate(getBuilders().get(0).getInitialOrbitDate());
  90.             }
  91.         }

  92.         // Increment measurement number
  93.         incrementCurrentMeasurementNumber();

  94.         // Update the current date
  95.         setCurrentDate(measurement.getObservedMeasurement().getDate());

  96.         // Initialize array of predicted sigma points
  97.         final RealVector[] predictedSigmaPoints = new RealVector[sigmaPoints.length];

  98.         // Propagate each sigma point
  99.         //
  100.         // We need to make a choice about what happens with the non-estimated parts of the orbital states.
  101.         // Here we've assumed that the zero'th sigma point is roughly the mean and keep those propagated
  102.         // orbital parameters.  This is why we loop backward through the sigma-points and don't reset the
  103.         // propagator builders on the last iteration (corresponding to the zero-th sigma point).
  104.         //
  105.         // Note that -not- resetting the builders on the last iteration means that their time-stamps correspond
  106.         // to the prediction time.  The assumption is that the unscented filter calls getEvolution, then
  107.         // getPredictedMeasurements, then getInnovation.
  108.         for (int i = sigmaPoints.length - 1; i >= 0; i--) {

  109.             // Set parameters for this sigma point
  110.             final RealVector sigmaPoint = sigmaPoints[i].copy();
  111.             updateParameters(sigmaPoint);

  112.             // Get propagators
  113.             final Propagator[] propagators = getEstimatedPropagators();

  114.             // Do prediction
  115.             predictedSigmaPoints[i] =
  116.                     predictState(observedMeasurement.getDate(), sigmaPoint, propagators, i != 0);
  117.         }

  118.         // Reset the driver reference values based on the first sigma point
  119.         int d = 0;
  120.         for (final DelegatingDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
  121.             driver.setReferenceValue(referenceValues[d]);
  122.             driver.setNormalizedValue(predictedSigmaPoints[0].getEntry(d));
  123.             referenceValues[d] = driver.getValue();

  124.             // Make remaining sigma points relative to the first
  125.             for (int i = 1; i < predictedSigmaPoints.length; ++i) {
  126.                 predictedSigmaPoints[i].setEntry(d, predictedSigmaPoints[i].getEntry(d) - predictedSigmaPoints[0].getEntry(d));
  127.             }
  128.             predictedSigmaPoints[0].setEntry(d, 0.0);

  129.             d += 1;
  130.         }

  131.         // Return
  132.         return new UnscentedEvolution(measurement.getTime(), predictedSigmaPoints);
  133.     }

  134.     /** {@inheritDoc} */
  135.     @Override
  136.     public RealMatrix getProcessNoiseMatrix(final double previousTime, final RealVector predictedState,
  137.                                             final MeasurementDecorator measurement) {
  138.         // Set parameters from predicted state
  139.         final RealVector predictedStateCopy = predictedState.copy();
  140.         updateParameters(predictedStateCopy);

  141.         // Get propagators
  142.         Propagator[] propagators = getEstimatedPropagators();

  143.         // "updateParameters" sets the correct orbital info, but doesn't reset the time.
  144.         for (int k = 0; k < propagators.length; ++k) {
  145.             final SpacecraftState predicted = propagators[k].getInitialState();
  146.             final Orbit predictedOrbit = getBuilders().get(k).getOrbitType().convertType(
  147.                     new CartesianOrbit(predicted.getPVCoordinates(),
  148.                             predicted.getFrame(),
  149.                             measurement.getObservedMeasurement().getDate(),
  150.                             predicted.getOrbit().getMu()
  151.                     )
  152.             );
  153.             getBuilders().get(k).resetOrbit(predictedOrbit);
  154.         }
  155.         propagators = getEstimatedPropagators();

  156.         // Predicted states
  157.         for (int k = 0; k < propagators.length; ++k) {
  158.             setPredictedSpacecraftState(propagators[k].getInitialState(), k);
  159.         }

  160.         return getNormalizedProcessNoise(predictedState.getDimension());
  161.     }

  162.     /** {@inheritDoc} */
  163.     @Override
  164.     public RealVector[] getPredictedMeasurements(final RealVector[] predictedSigmaPoints, final MeasurementDecorator measurement) {

  165.         // Observed measurement
  166.         final ObservedMeasurement<?> observedMeasurement = measurement.getObservedMeasurement();

  167.         // Standard deviation as a vector
  168.         final RealVector theoreticalStandardDeviation =
  169.                 MatrixUtils.createRealVector(observedMeasurement.getTheoreticalStandardDeviation());

  170.         // Initialize arrays of predicted states and measurements
  171.         final RealVector[] predictedMeasurements = new RealVector[predictedSigmaPoints.length];

  172.         // Loop on sigma points to predict measurements
  173.         for (int i = 0; i < predictedSigmaPoints.length; ++i) {
  174.             // Set parameters for this sigma point
  175.             final RealVector predictedSigmaPoint = predictedSigmaPoints[i].copy();
  176.             updateParameters(predictedSigmaPoint);

  177.             // Get propagators
  178.             final Propagator[] propagators = getEstimatedPropagators();

  179.             // Predicted states
  180.             final SpacecraftState[] predictedStates = new SpacecraftState[propagators.length];
  181.             for (int k = 0; k < propagators.length; ++k) {
  182.                 predictedStates[k] = propagators[k].getInitialState();
  183.             }

  184.             // Calculated estimated measurement from predicted sigma point
  185.             final EstimatedMeasurement<?> estimated = estimateMeasurement(observedMeasurement, getCurrentMeasurementNumber(),
  186.                                                                                    KalmanEstimatorUtil.filterRelevant(observedMeasurement,
  187.                                                                                                                       predictedStates));
  188.             predictedMeasurements[i] = new ArrayRealVector(estimated.getEstimatedValue())
  189.                     .ebeDivide(theoreticalStandardDeviation);
  190.         }

  191.         // Return the predicted measurements
  192.         return predictedMeasurements;

  193.     }

  194.     /** {@inheritDoc} */
  195.     @Override
  196.     public RealVector getInnovation(final MeasurementDecorator measurement, final RealVector predictedMeas,
  197.                                     final RealVector predictedState, final RealMatrix innovationCovarianceMatrix) {
  198.         // Standard deviation as a vector
  199.         final RealVector theoreticalStandardDeviation =
  200.                 MatrixUtils.createRealVector(measurement.getObservedMeasurement().getTheoreticalStandardDeviation());

  201.         // Get propagators
  202.         final Propagator[] propagators = getEstimatedPropagators();

  203.         // Predicted states
  204.         for (int k = 0; k < propagators.length; ++k) {
  205.             setPredictedSpacecraftState(propagators[k].getInitialState(), k);
  206.         }

  207.         // set estimated value to the predicted value from the filter
  208.         final EstimatedMeasurement<?> predictedMeasurement =
  209.             estimateMeasurement(measurement.getObservedMeasurement(), getCurrentMeasurementNumber(),
  210.                                 KalmanEstimatorUtil.filterRelevant(measurement.getObservedMeasurement(),
  211.                                 getPredictedSpacecraftStates()));
  212.         setPredictedMeasurement(predictedMeasurement);
  213.         predictedMeasurement.setEstimatedValue(predictedMeas.ebeMultiply(theoreticalStandardDeviation).toArray());

  214.         // Check for outliers
  215.         KalmanEstimatorUtil.applyDynamicOutlierFilter(predictedMeasurement, innovationCovarianceMatrix);

  216.         // Compute the innovation vector
  217.         return KalmanEstimatorUtil.computeInnovationVector(predictedMeasurement,
  218.                 predictedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
  219.     }


  220.     private RealVector predictState(final AbsoluteDate date,
  221.                                     final RealVector previousState,
  222.                                     final Propagator[] propagators,
  223.                                     final boolean resetState) {

  224.         // Initialise predicted state
  225.         final RealVector predictedState = previousState.copy();

  226.         // Orbital parameters counter
  227.         int jOrb = 0;

  228.         // Loop over propagators
  229.         for (int k = 0; k < propagators.length; ++k) {

  230.             // Record original state
  231.             final SpacecraftState originalState = propagators[k].getInitialState();

  232.             // Propagate
  233.             final SpacecraftState predicted = propagators[k].propagate(date);

  234.             // Update the builder with the predicted orbit
  235.             // This updates the orbital drivers with the values of the predicted orbit
  236.             getBuilders().get(k).resetOrbit(predicted.getOrbit());

  237.             // Additionally, for PropagatorBuilders which use mass, update the builder with the predicted mass value.
  238.             // If any mass changes have occurred during this estimation step, such as maneuvers,
  239.             // the updated mass value must be carried over so that new Propagators from this builder start with the updated mass.
  240.             if (getBuilders().get(k) instanceof AbstractPropagatorBuilder) {
  241.                 ((AbstractPropagatorBuilder<?>) (getBuilders().get(k))).setMass(predicted.getMass());
  242.             }

  243.             // The orbital parameters in the state vector are replaced with their predicted values
  244.             // The propagation & measurement parameters are not changed by the prediction (i.e. the propagation)
  245.             // As the propagator builder was previously updated with the predicted orbit,
  246.             // the selected orbital drivers are already up to date with the prediction
  247.             for (DelegatingDriver orbitalDriver : getBuilders().get(k).getOrbitalParametersDrivers().getDrivers()) {
  248.                 if (orbitalDriver.isSelected()) {
  249.                     orbitalDriver.setReferenceValue(referenceValues[jOrb]);
  250.                     predictedState.setEntry(jOrb, orbitalDriver.getNormalizedValue());

  251.                     jOrb += 1;
  252.                 }
  253.             }

  254.             // Set the builder back to the original time
  255.             if (resetState) {
  256.                 getBuilders().get(k).resetOrbit(originalState.getOrbit());
  257.             }
  258.         }

  259.         return predictedState;
  260.     }


  261.     /** Finalize estimation.
  262.      * @param observedMeasurement measurement that has just been processed
  263.      * @param estimate corrected estimate
  264.      */
  265.     public void finalizeEstimation(final ObservedMeasurement<?> observedMeasurement,
  266.                                    final ProcessEstimate estimate) {
  267.         // Update the parameters with the estimated state
  268.         // The min/max values of the parameters are handled by the ParameterDriver implementation
  269.         setCorrectedEstimate(estimate);
  270.         updateParameters(estimate.getState());

  271.         // Get the estimated propagator (mirroring parameter update in the builder)
  272.         // and the estimated spacecraft state
  273.         final Propagator[] estimatedPropagators = getEstimatedPropagators();
  274.         for (int k = 0; k < estimatedPropagators.length; ++k) {
  275.             setCorrectedSpacecraftState(estimatedPropagators[k].getInitialState(), k);
  276.         }

  277.         // Corrected measurement
  278.         setCorrectedMeasurement(estimateMeasurement(observedMeasurement, getCurrentMeasurementNumber(),
  279.                                                     KalmanEstimatorUtil.filterRelevant(observedMeasurement,
  280.                                                     getCorrectedSpacecraftStates())));
  281.     }

  282.     /**
  283.      * Estimate measurement (without derivatives).
  284.      * @param <T> measurement type
  285.      * @param observedMeasurement observed measurement
  286.      * @param measurementNumber measurement number
  287.      * @param spacecraftStates states
  288.      * @return estimated measurement
  289.      * @since 12.1
  290.      */
  291.     private static <T extends ObservedMeasurement<T>> EstimatedMeasurement<T> estimateMeasurement(final ObservedMeasurement<T> observedMeasurement,
  292.                                                                                                   final int measurementNumber,
  293.                                                                                                   final SpacecraftState[] spacecraftStates) {
  294.         final EstimatedMeasurementBase<T> estimatedMeasurementBase = observedMeasurement.
  295.                 estimateWithoutDerivatives(measurementNumber, measurementNumber,
  296.                 KalmanEstimatorUtil.filterRelevant(observedMeasurement, spacecraftStates));
  297.         return new EstimatedMeasurement<>(estimatedMeasurementBase);
  298.     }

  299.     /** Update parameter drivers with a normalised state, adjusting state according to the driver limits.
  300.      * @param normalizedState the input state
  301.      * The min/max allowed values are handled by the parameter themselves.
  302.      */
  303.     private void updateParameters(final RealVector normalizedState) {
  304.         int i = 0;
  305.         for (final DelegatingDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
  306.             // let the parameter handle min/max clipping
  307.             driver.setReferenceValue(referenceValues[i]);
  308.             driver.setNormalizedValue(normalizedState.getEntry(i));
  309.             normalizedState.setEntry(i++, driver.getNormalizedValue());
  310.         }
  311.         for (final DelegatingDriver driver : getEstimatedPropagationParameters().getDrivers()) {
  312.             // let the parameter handle min/max clipping
  313.             driver.setNormalizedValue(normalizedState.getEntry(i));
  314.             normalizedState.setEntry(i++, driver.getNormalizedValue());
  315.         }
  316.         for (final DelegatingDriver driver : getEstimatedMeasurementsParameters().getDrivers()) {
  317.             // let the parameter handle min/max clipping
  318.             driver.setNormalizedValue(normalizedState.getEntry(i));
  319.             normalizedState.setEntry(i++, driver.getNormalizedValue());
  320.         }
  321.     }
  322. }