RtsSmoother.java

  1. /* Copyright 2002-2024 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.KalmanSmoother;
  19. import org.hipparchus.filtering.kalman.ProcessEstimate;
  20. import org.hipparchus.linear.MatrixUtils;
  21. import org.hipparchus.linear.RealMatrix;
  22. import org.hipparchus.linear.RealVector;
  23. import org.orekit.time.AbsoluteDate;
  24. import org.orekit.utils.ParameterDriver;
  25. import org.orekit.utils.ParameterDriversList;

  26. import java.util.ArrayList;
  27. import java.util.List;

  28. /** Perform an RTS (Rauch-Tung-Striebel) smoothing step over results from a sequential estimator.
  29.  *
  30.  * <p>The Kalman and Unscented sequential estimators produce a state (mean and covariance) after processing each
  31.  * measurement.  This state is a statistical summary of all the information provided to the filter, from the
  32.  * measurements and model of the spacecraft motion, up until the latest measurement.  A smoother produces estimates that
  33.  * are summaries of information over <em>all</em> measurements, both past and future.</p>
  34.  *
  35.  * <p>For example, if a filter processes
  36.  * measurements from time 1 to 10, then the filter state at time 5 uses measurement information up to time 5, while
  37.  * the smoother state at time 5 uses measurement information from the entire interval, times 1 to 10.  This typically
  38.  * results in more accurate estimates, with more information reducing the uncertainty.</p>
  39.  *
  40.  * <p>This smoother is implemented using the {@link KalmanObserver} mechanism.  The
  41.  * smoother collects data from the forward estimation over the measurements, then applies a backward pass to
  42.  * calculate the smoothed estimates.  Smoothed estimates are collected into a list of
  43.  * {@link PhysicalEstimatedState}, containing a timestamp, mean and covariance over all estimated parameters
  44.  * (orbital, propagation and measurement).  The order of the parameters in these states is the same as the
  45.  * underlying sequential estimator, for example from a call to {@link KalmanEstimator#getPhysicalEstimatedState()}.</p>
  46.  *
  47.  * <p>The smoother is compatible with the Kalman and Unscented sequential estimators, but does not support the
  48.  * semi-analytical equivalents.</p>
  49.  *
  50.  * <p>The following code snippet demonstrates how to attach the smoother to a filter and retrieve smoothed states:</p>
  51.  *
  52.  * <pre>
  53.  *     // Build the Kalman filter
  54.  *     final KalmanEstimator kalmanEstimator = new KalmanEstimatorBuilder().
  55.  *         addPropagationConfiguration(propagatorBuilder, new ConstantProcessNoise(initialP, Q)).
  56.  *         build();
  57.  *
  58.  *     // Add smoother observer to filter
  59.  *     final RtsSmoother rtsSmoother = new RtsSmoother(kalmanEstimator);
  60.  *     kalmanEstimator.setObserver(rtsSmoother);
  61.  *
  62.  *     // Perform forward filtering over the measurements
  63.  *     Propagator[] estimated = kalmanEstimator.processMeasurements(measurements);
  64.  *
  65.  *     // Perform backwards smoothing and collect the results
  66.  *     rtsSmoother.backwardsSmooth();
  67.  * </pre>
  68.  *
  69.  * <p>Note that the smoother stores data from every filter step, leading to high memory usage for long-duration runs
  70.  * with numerous measurements.</p>
  71.  *
  72.  * @see KalmanEstimatorBuilder
  73.  * @see UnscentedKalmanEstimatorBuilder
  74.  * @see "S&auml;rkk&auml; S. Bayesian Filtering and Smoothing. Cambridge University Press, 2013."
  75.  * @author Mark Rutten
  76.  * @since 13.0
  77.  */
  78. public class RtsSmoother implements KalmanObserver {

  79.     /** Smoother. */
  80.     private final KalmanSmoother smoother;

  81.     /** Estimator reference date. */
  82.     private final AbsoluteDate referenceDate;

  83.     /** Covariance scales for unnormalising estimates. */
  84.     private final double[] covarianceScale;

  85.     /** Estimated orbital parameters. */
  86.     private final ParameterDriversList estimatedOrbitalParameters;

  87.     /** Estimated propagation drivers. */
  88.     private final ParameterDriversList estimatedPropagationParameters;

  89.     /** Estimated measurements parameters. */
  90.     private final ParameterDriversList estimatedMeasurementsParameters;

  91.     /** Reference states for unnormalising estimates. */
  92.     private final List<RealVector> referenceStates;

  93.     /** Smoother observer constructor from a sequential estimator.
  94.      * This smoother constructor requires access to the underlying estimator to initialise some information not
  95.      * available from {@link KalmanEstimation} during {@link RtsSmoother#init}, including the estimated parameters
  96.      * drivers (orbital, propagation and measurements).
  97.      * @param estimator the Kalman estimator
  98.      */
  99.     public RtsSmoother(final AbstractKalmanEstimator estimator) {
  100.         this.smoother = new KalmanSmoother(estimator.getMatrixDecomposer());
  101.         this.referenceDate = estimator.getReferenceDate();
  102.         this.covarianceScale = estimator.getScale();
  103.         this.estimatedOrbitalParameters = estimator.getOrbitalParametersDrivers(true);
  104.         this.estimatedPropagationParameters = estimator.getPropagationParametersDrivers(true);
  105.         this.estimatedMeasurementsParameters = estimator.getEstimatedMeasurementsParameters();
  106.         this.referenceStates = new ArrayList<>();

  107.         // Add smoother observer to underlying filter
  108.         estimator.getKalmanFilter().setObserver(smoother);
  109.     }

  110.     /** {@inheritDoc}
  111.      */
  112.     @Override
  113.     public void init(final KalmanEstimation estimation) {
  114.         // Get the first reference state
  115.         referenceStates.add(getReferenceState());
  116.     }

  117.     /** {@inheritDoc} This accumulates the filter states as the sequential estimator processes measurements.
  118.      */
  119.     @Override
  120.     public void evaluationPerformed(final KalmanEstimation estimation) {
  121.         referenceStates.add(getReferenceState());
  122.     }

  123.     /** Perform a RTS backwards smoothing recursion over the filtered states collected by the observer.
  124.      * @return a list of {@link PhysicalEstimatedState}
  125.      */
  126.     public List<PhysicalEstimatedState> backwardsSmooth() {

  127.         // Backwards smoothing step
  128.         final List<ProcessEstimate> normalisedStates = smoother.backwardsSmooth();

  129.         // Convert to physical states
  130.         final List<PhysicalEstimatedState> smoothedStates = new ArrayList<>();
  131.         for (int timeIndex = 0; timeIndex < normalisedStates.size(); ++timeIndex) {
  132.             final ProcessEstimate normalisedState =  normalisedStates.get(timeIndex);
  133.             final RealVector physicalState =
  134.                     getPhysicalState(normalisedState.getState(), referenceStates.get(timeIndex));
  135.             final RealMatrix physicalCovariance =
  136.                     KalmanEstimatorUtil.unnormalizeCovarianceMatrix(normalisedState.getCovariance(), covarianceScale);
  137.             smoothedStates.add(new PhysicalEstimatedState(
  138.                     referenceDate.shiftedBy(normalisedState.getTime()),
  139.                     physicalState,
  140.                     physicalCovariance
  141.             ));
  142.         }
  143.         return smoothedStates;
  144.     }

  145.     /** Get reference values from the estimation parameter drivers.
  146.      * @return the reference state
  147.      */
  148.     private RealVector getReferenceState() {
  149.         final RealVector referenceState = MatrixUtils.createRealVector(covarianceScale.length);
  150.         int i = 0;
  151.         for (final ParameterDriversList.DelegatingDriver driver : estimatedOrbitalParameters.getDrivers()) {
  152.             referenceState.setEntry(i++, driver.getReferenceValue());
  153.         }
  154.         for (final ParameterDriversList.DelegatingDriver driver : estimatedPropagationParameters.getDrivers()) {
  155.             referenceState.setEntry(i++, driver.getReferenceValue());
  156.         }
  157.         for (final ParameterDriversList.DelegatingDriver driver : estimatedMeasurementsParameters.getDrivers()) {
  158.             referenceState.setEntry(i++, driver.getReferenceValue());
  159.         }
  160.         return referenceState;
  161.     }


  162.     /** Get reference values from the estimation parameter drivers.
  163.      * @param normalisedState the normalised state
  164.      * @param referenceState the reference state
  165.      * @return the reference state
  166.      */
  167.     private RealVector getPhysicalState(final RealVector normalisedState, final RealVector referenceState) {
  168.         final RealVector physicalState = MatrixUtils.createRealVector(covarianceScale.length);
  169.         int i = 0;
  170.         for (final ParameterDriversList.DelegatingDriver driver : estimatedOrbitalParameters.getDrivers()) {
  171.             physicalState.setEntry(i, setResetDriver(driver, referenceState.getEntry(i), normalisedState.getEntry(i)));
  172.             i += 1;
  173.         }
  174.         for (final ParameterDriversList.DelegatingDriver driver : estimatedPropagationParameters.getDrivers()) {
  175.             physicalState.setEntry(i, setResetDriver(driver, referenceState.getEntry(i), normalisedState.getEntry(i)));
  176.             i += 1;
  177.         }
  178.         for (final ParameterDriversList.DelegatingDriver driver : estimatedMeasurementsParameters.getDrivers()) {
  179.             physicalState.setEntry(i, setResetDriver(driver, referenceState.getEntry(i), normalisedState.getEntry(i)));
  180.             i += 1;
  181.         }
  182.         return physicalState;
  183.     }

  184.     /** Use a driver to extract a new value, given reference and normalised values,
  185.      * resetting the state of the driver afterward.
  186.      * @param driver the parameter driver
  187.      * @param referenceValue the new reference value
  188.      * @param normalisedVale the new normalised value
  189.      * @return the reference state
  190.      */
  191.     private double setResetDriver(final ParameterDriver driver,
  192.                                   final double referenceValue,
  193.                                   final double normalisedVale) {
  194.         // Record old driver parameters
  195.         final double oldReference = driver.getReferenceValue();
  196.         final double oldValue = driver.getNormalizedValue();

  197.         // Set driver to new parameters
  198.         driver.setReferenceValue(referenceValue);
  199.         driver.setNormalizedValue(normalisedVale);
  200.         final double physicalValue = driver.getValue();

  201.         // Reset driver to old
  202.         driver.setReferenceValue(oldReference);
  203.         driver.setNormalizedValue(oldValue);

  204.         return physicalValue;
  205.     }

  206. }