StateCovarianceBlender.java

/* Copyright 2002-2024 CS GROUP
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.propagation;

import org.hipparchus.analysis.polynomials.SmoothStepFactory;
import org.hipparchus.linear.RealMatrix;
import org.orekit.frames.Frame;
import org.orekit.frames.LOFType;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.OrbitType;
import org.orekit.orbits.PositionAngleType;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeInterpolator;
import org.orekit.time.TimeStampedPair;

import java.util.List;

/**
 * State covariance blender.
 * <p>
 * Its purpose is to interpolate state covariance between tabulated state covariances by using the concept of blending,
 * exposed in : "Efficient Covariance Interpolation using Blending of Approximate State Error Transitions" by Sergei
 * Tanygin.
 * <p>
 * It propagates tabulated values to the interpolation date assuming a standard keplerian model and then blend each
 * propagated covariances using a smoothstep function.
 * <p>
 * It gives accurate results as explained <a
 * href="https://orekit.org/doc/technical-notes/Implementation_of_covariance_interpolation_in_Orekit.pdf">here</a>. In the
 * very poorly tracked test case evolving in a highly dynamical environment mentioned in the linked thread, the user can
 * expect at worst errors of less than 0.25% in position sigmas and less than 0.4% in velocity sigmas with steps of 40mn
 * between tabulated values.
 *
 * @author Vincent Cucchietti
 * @see org.hipparchus.analysis.polynomials.SmoothStepFactory
 * @see org.hipparchus.analysis.polynomials.SmoothStepFactory.SmoothStepFunction
 */
public class StateCovarianceBlender extends AbstractStateCovarianceInterpolator {

    /** Blending function. */
    private final SmoothStepFactory.SmoothStepFunction blendingFunction;

    /**
     * Constructor.
     * <p>
     * <b>BEWARE:</b> If the output local orbital frame is not considered pseudo-inertial, all the covariance components
     * related to the velocity will be poorly interpolated. <b>Only the position covariance should be considered in this
     * case.</b>
     *
     * @param blendingFunction blending function
     * @param orbitInterpolator orbit interpolator
     * @param outLOF local orbital frame
     *
     * @see Frame
     * @see OrbitType
     * @see PositionAngleType
     */
    public StateCovarianceBlender(final SmoothStepFactory.SmoothStepFunction blendingFunction,
                                  final TimeInterpolator<Orbit> orbitInterpolator,
                                  final LOFType outLOF) {
        super(DEFAULT_INTERPOLATION_POINTS, 0., orbitInterpolator, outLOF);
        this.blendingFunction = blendingFunction;
    }

    /**
     * Constructor.
     *
     * @param blendingFunction blending function
     * @param orbitInterpolator orbit interpolator
     * @param outFrame desired output covariance frame
     * @param outPositionAngleType desired output position angle
     * @param outOrbitType desired output orbit type
     *
     * @see Frame
     * @see OrbitType
     * @see PositionAngleType
     */
    public StateCovarianceBlender(final SmoothStepFactory.SmoothStepFunction blendingFunction,
                                  final TimeInterpolator<Orbit> orbitInterpolator,
                                  final Frame outFrame,
                                  final OrbitType outOrbitType,
                                  final PositionAngleType outPositionAngleType) {
        super(DEFAULT_INTERPOLATION_POINTS, 0., orbitInterpolator, outFrame, outOrbitType, outPositionAngleType);
        this.blendingFunction = blendingFunction;
    }

    /** {@inheritDoc} */
    @Override
    protected StateCovariance computeInterpolatedCovarianceInOrbitFrame(
            final List<TimeStampedPair<Orbit, StateCovariance>> uncertainStates,
            final Orbit interpolatedOrbit) {

        // Necessarily only two sample for blending
        final TimeStampedPair<Orbit, StateCovariance> previousUncertainState = uncertainStates.get(0);
        final TimeStampedPair<Orbit, StateCovariance> nextUncertainState     = uncertainStates.get(1);

        // Get the interpolation date
        final AbsoluteDate interpolationDate = interpolatedOrbit.getDate();

        // Propagate previous tabulated covariance to interpolation date
        final StateCovariance forwardedCovariance =
                propagateCovarianceAnalytically(interpolationDate, interpolatedOrbit, previousUncertainState);

        // Propagate next tabulated covariance to interpolation date
        final StateCovariance backwardedCovariance =
                propagateCovarianceAnalytically(interpolationDate, interpolatedOrbit, nextUncertainState);

        // Compute normalized time parameter
        final double timeParameter =
                getTimeParameter(interpolationDate, previousUncertainState.getDate(), nextUncertainState.getDate());

        // Blend the covariance matrices
        final double     blendingValue              = blendingFunction.value(timeParameter);
        final RealMatrix forwardedCovarianceMatrix  = forwardedCovariance.getMatrix();
        final RealMatrix backwardedCovarianceMatrix = backwardedCovariance.getMatrix();

        final RealMatrix blendedCovarianceMatrix =
                forwardedCovarianceMatrix.blendArithmeticallyWith(backwardedCovarianceMatrix, blendingValue);

        return new StateCovariance(blendedCovarianceMatrix, interpolationDate, interpolatedOrbit.getFrame(),
                                   OrbitType.CARTESIAN, DEFAULT_POSITION_ANGLE);
    }

    /**
     * Propagate given state covariance to the interpolation date using keplerian motion.
     *
     * @param interpolationTime interpolation date at which we desire to interpolate the state covariance
     * @param orbitAtInterpolatingTime orbit at interpolation date
     * @param tabulatedUncertainState tabulated uncertain state
     *
     * @return state covariance at given interpolation date.
     *
     * @see StateCovariance
     */
    private StateCovariance propagateCovarianceAnalytically(final AbsoluteDate interpolationTime,
                                                            final Orbit orbitAtInterpolatingTime,
                                                            final TimeStampedPair<Orbit, StateCovariance> tabulatedUncertainState) {

        // Get orbit and covariance
        final Orbit           tabulatedOrbit         = tabulatedUncertainState.getFirst();
        final StateCovariance tabulatedCovariance    = tabulatedUncertainState.getSecond();
        final Frame           interpolatedOrbitFrame = orbitAtInterpolatingTime.getFrame();

        // Express tabulated covariance in interpolated orbit frame for consistency
        final StateCovariance tabulatedCovarianceInOrbitFrame =
                tabulatedCovariance.changeCovarianceFrame(tabulatedOrbit, interpolatedOrbitFrame);

        // First convert the covariance matrix to equinoctial elements to avoid singularities inherent to keplerian elements
        final RealMatrix covarianceMatrixInEquinoctial =
                tabulatedCovarianceInOrbitFrame.changeCovarianceType(tabulatedOrbit, OrbitType.EQUINOCTIAL,
                                                                     DEFAULT_POSITION_ANGLE).getMatrix();

        // Compute state error transition matrix in equinoctial elements (identical to the one in keplerian elements)
        final RealMatrix stateErrorTransitionMatrixInEquinoctial =
                StateCovariance.getStm(tabulatedOrbit, interpolationTime.durationFrom(tabulatedOrbit.getDate()));

        // Propagate the covariance matrix using the previously computed state error transition matrix
        final RealMatrix propagatedCovarianceMatrixInEquinoctial =
                stateErrorTransitionMatrixInEquinoctial.multiply(
                        covarianceMatrixInEquinoctial.multiplyTransposed(stateErrorTransitionMatrixInEquinoctial));

        // Recreate a StateCovariance instance
        final StateCovariance propagatedCovarianceInEquinoctial =
                new StateCovariance(propagatedCovarianceMatrixInEquinoctial, interpolationTime,
                                    interpolatedOrbitFrame, OrbitType.EQUINOCTIAL, DEFAULT_POSITION_ANGLE);

        // Output propagated state covariance after converting back to cartesian elements
        return propagatedCovarianceInEquinoctial.changeCovarianceType(orbitAtInterpolatingTime,
                                                                      OrbitType.CARTESIAN, DEFAULT_POSITION_ANGLE);
    }
}