SemiAnalyticalKalmanModel.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.exception.MathRuntimeException;
  19. import org.hipparchus.filtering.kalman.ProcessEstimate;
  20. import org.hipparchus.filtering.kalman.extended.ExtendedKalmanFilter;
  21. import org.hipparchus.filtering.kalman.extended.NonLinearEvolution;
  22. import org.hipparchus.filtering.kalman.extended.NonLinearProcess;
  23. import org.hipparchus.linear.Array2DRowRealMatrix;
  24. import org.hipparchus.linear.ArrayRealVector;
  25. import org.hipparchus.linear.MatrixUtils;
  26. import org.hipparchus.linear.QRDecomposition;
  27. import org.hipparchus.linear.RealMatrix;
  28. import org.hipparchus.linear.RealVector;
  29. import org.hipparchus.util.FastMath;
  30. import org.orekit.errors.OrekitException;
  31. import org.orekit.estimation.measurements.EstimatedMeasurement;
  32. import org.orekit.estimation.measurements.ObservedMeasurement;
  33. import org.orekit.orbits.Orbit;
  34. import org.orekit.orbits.OrbitType;
  35. import org.orekit.propagation.PropagationType;
  36. import org.orekit.propagation.SpacecraftState;
  37. import org.orekit.propagation.conversion.DSSTPropagatorBuilder;
  38. import org.orekit.propagation.semianalytical.dsst.DSSTHarvester;
  39. import org.orekit.propagation.semianalytical.dsst.DSSTPropagator;
  40. import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
  41. import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
  42. import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
  43. import org.orekit.time.AbsoluteDate;
  44. import org.orekit.time.ChronologicalComparator;
  45. import org.orekit.utils.ParameterDriver;
  46. import org.orekit.utils.ParameterDriversList;
  47. import org.orekit.utils.ParameterDriversList.DelegatingDriver;
  48. import org.orekit.utils.TimeSpanMap.Span;

  49. import java.util.ArrayList;
  50. import java.util.Comparator;
  51. import java.util.HashMap;
  52. import java.util.List;
  53. import java.util.Map;

  54. /** Process model to use with a {@link SemiAnalyticalKalmanEstimator}.
  55.  *
  56.  * @see "Folcik Z., Orbit Determination Using Modern Filters/Smoothers and Continuous Thrust Modeling,
  57.  *       Master of Science Thesis, Department of Aeronautics and Astronautics, MIT, June, 2008."
  58.  *
  59.  * @see "Cazabonne B., Bayard J., Journot M., and Cefola P. J., A Semi-analytical Approach for Orbit
  60.  *       Determination based on Extended Kalman Filter, AAS Paper 21-614, AAS/AIAA Astrodynamics
  61.  *       Specialist Conference, Big Sky, August 2021."
  62.  *
  63.  * @author Julie Bayard
  64.  * @author Bryan Cazabonne
  65.  * @author Maxime Journot
  66.  * @since 11.1
  67.  */
  68. public  class SemiAnalyticalKalmanModel implements KalmanEstimation, NonLinearProcess<MeasurementDecorator>, SemiAnalyticalProcess {

  69.     /** Builders for DSST propagator. */
  70.     private final DSSTPropagatorBuilder builder;

  71.     /** Estimated orbital parameters. */
  72.     private final ParameterDriversList estimatedOrbitalParameters;

  73.     /** Per-builder estimated propagation drivers. */
  74.     private final ParameterDriversList estimatedPropagationParameters;

  75.     /** Estimated measurements parameters. */
  76.     private final ParameterDriversList estimatedMeasurementsParameters;

  77.     /** Map for propagation parameters columns. */
  78.     private final Map<String, Integer> propagationParameterColumns;

  79.     /** Map for measurements parameters columns. */
  80.     private final Map<String, Integer> measurementParameterColumns;

  81.     /** Scaling factors. */
  82.     private final double[] scale;

  83.     /** Provider for covariance matrix. */
  84.     private final CovarianceMatrixProvider covarianceMatrixProvider;

  85.     /** Process noise matrix provider for measurement parameters. */
  86.     private final CovarianceMatrixProvider measurementProcessNoiseMatrix;

  87.     /** Harvester between two-dimensional Jacobian matrices and one-dimensional additional state arrays. */
  88.     private DSSTHarvester harvester;

  89.     /** Propagators for the reference trajectories, up to current date. */
  90.     private DSSTPropagator dsstPropagator;

  91.     /** Observer to retrieve current estimation info. */
  92.     private KalmanObserver observer;

  93.     /** Current number of measurement. */
  94.     private int currentMeasurementNumber;

  95.     /** Current date. */
  96.     private AbsoluteDate currentDate;

  97.     /** Predicted mean element filter correction. */
  98.     private RealVector predictedFilterCorrection;

  99.     /** Corrected mean element filter correction. */
  100.     private RealVector correctedFilterCorrection;

  101.     /** Predicted measurement. */
  102.     private EstimatedMeasurement<?> predictedMeasurement;

  103.     /** Corrected measurement. */
  104.     private EstimatedMeasurement<?> correctedMeasurement;

  105.     /** Nominal mean spacecraft state. */
  106.     private SpacecraftState nominalMeanSpacecraftState;

  107.     /** Previous nominal mean spacecraft state. */
  108.     private SpacecraftState previousNominalMeanSpacecraftState;

  109.     /** Current corrected estimate. */
  110.     private ProcessEstimate correctedEstimate;

  111.     /** Inverse of the orbital part of the state transition matrix. */
  112.     private RealMatrix phiS;

  113.     /** Propagation parameters part of the state transition matrix. */
  114.     private RealMatrix psiS;

  115.     /** Kalman process model constructor (package private).
  116.      * @param propagatorBuilder propagators builders used to evaluate the orbits.
  117.      * @param covarianceMatrixProvider provider for covariance matrix
  118.      * @param estimatedMeasurementParameters measurement parameters to estimate
  119.      * @param measurementProcessNoiseMatrix provider for measurement process noise matrix
  120.      */
  121.     protected SemiAnalyticalKalmanModel(final DSSTPropagatorBuilder propagatorBuilder,
  122.                                         final CovarianceMatrixProvider covarianceMatrixProvider,
  123.                                         final ParameterDriversList estimatedMeasurementParameters,
  124.                                         final CovarianceMatrixProvider measurementProcessNoiseMatrix) {

  125.         this.builder                         = propagatorBuilder;
  126.         this.estimatedMeasurementsParameters = estimatedMeasurementParameters;
  127.         this.measurementParameterColumns     = new HashMap<>(estimatedMeasurementsParameters.getDrivers().size());
  128.         this.observer                        = null;
  129.         this.currentMeasurementNumber        = 0;
  130.         this.currentDate                     = propagatorBuilder.getInitialOrbitDate();
  131.         this.covarianceMatrixProvider        = covarianceMatrixProvider;
  132.         this.measurementProcessNoiseMatrix   = measurementProcessNoiseMatrix;

  133.         // Number of estimated parameters
  134.         int columns = 0;

  135.         // Set estimated orbital parameters
  136.         estimatedOrbitalParameters = new ParameterDriversList();
  137.         for (final ParameterDriver driver : builder.getOrbitalParametersDrivers().getDrivers()) {

  138.             // Verify if the driver reference date has been set
  139.             if (driver.getReferenceDate() == null) {
  140.                 driver.setReferenceDate(currentDate);
  141.             }

  142.             // Verify if the driver is selected
  143.             if (driver.isSelected()) {
  144.                 estimatedOrbitalParameters.add(driver);
  145.                 columns++;
  146.             }

  147.         }

  148.         // Set estimated propagation parameters
  149.         estimatedPropagationParameters = new ParameterDriversList();
  150.         final List<String> estimatedPropagationParametersNames = new ArrayList<>();
  151.         for (final ParameterDriver driver : builder.getPropagationParametersDrivers().getDrivers()) {

  152.             // Verify if the driver reference date has been set
  153.             if (driver.getReferenceDate() == null) {
  154.                 driver.setReferenceDate(currentDate);
  155.             }

  156.             // Verify if the driver is selected
  157.             if (driver.isSelected()) {
  158.                 estimatedPropagationParameters.add(driver);
  159.                 // Add the driver name if it has not been added yet
  160.                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {

  161.                     if (!estimatedPropagationParametersNames.contains(span.getData())) {
  162.                         estimatedPropagationParametersNames.add(span.getData());
  163.                     }
  164.                 }
  165.             }

  166.         }
  167.         estimatedPropagationParametersNames.sort(Comparator.naturalOrder());

  168.         // Populate the map of propagation drivers' columns and update the total number of columns
  169.         propagationParameterColumns = new HashMap<>(estimatedPropagationParametersNames.size());
  170.         for (final String driverName : estimatedPropagationParametersNames) {
  171.             propagationParameterColumns.put(driverName, columns);
  172.             ++columns;
  173.         }

  174.         // Set the estimated measurement parameters
  175.         for (final ParameterDriver parameter : estimatedMeasurementsParameters.getDrivers()) {
  176.             if (parameter.getReferenceDate() == null) {
  177.                 parameter.setReferenceDate(currentDate);
  178.             }
  179.             for (Span<String> span = parameter.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  180.                 measurementParameterColumns.put(span.getData(), columns);
  181.                 ++columns;
  182.             }
  183.         }

  184.         // Compute the scale factors
  185.         this.scale = new double[columns];
  186.         int index = 0;
  187.         for (final ParameterDriver driver : estimatedOrbitalParameters.getDrivers()) {
  188.             scale[index++] = driver.getScale();
  189.         }
  190.         for (final ParameterDriver driver : estimatedPropagationParameters.getDrivers()) {
  191.             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  192.                 scale[index++] = driver.getScale();
  193.             }
  194.         }
  195.         for (final ParameterDriver driver : estimatedMeasurementsParameters.getDrivers()) {
  196.             for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  197.                 scale[index++] = driver.getScale();
  198.             }
  199.         }

  200.         // Build the reference propagator and add its partial derivatives equations implementation
  201.         updateReferenceTrajectory(getEstimatedPropagator());
  202.         this.nominalMeanSpacecraftState = dsstPropagator.getInitialState();
  203.         this.previousNominalMeanSpacecraftState = nominalMeanSpacecraftState;

  204.         // Initialize "field" short periodic terms
  205.         harvester.initializeFieldShortPeriodTerms(nominalMeanSpacecraftState);

  206.         // Initialize the estimated normalized mean element filter correction (See Ref [1], Eq. 3.2a)
  207.         this.predictedFilterCorrection = MatrixUtils.createRealVector(columns);
  208.         this.correctedFilterCorrection = predictedFilterCorrection;

  209.         // Initialize propagation parameters part of the state transition matrix (See Ref [1], Eq. 3.2c)
  210.         this.psiS = null;
  211.         if (estimatedPropagationParameters.getNbParams() != 0) {
  212.             this.psiS = MatrixUtils.createRealMatrix(getNumberSelectedOrbitalDriversValuesToEstimate(),
  213.                                                      getNumberSelectedPropagationDriversValuesToEstimate());
  214.         }

  215.         // Initialize inverse of the orbital part of the state transition matrix (See Ref [1], Eq. 3.2d)
  216.         this.phiS = MatrixUtils.createRealIdentityMatrix(getNumberSelectedOrbitalDriversValuesToEstimate());

  217.         // Number of estimated measurement parameters
  218.         final int nbMeas = getNumberSelectedMeasurementDriversValuesToEstimate();

  219.         // Number of estimated dynamic parameters (orbital + propagation)
  220.         final int nbDyn  = getNumberSelectedOrbitalDriversValuesToEstimate() + getNumberSelectedPropagationDriversValuesToEstimate();

  221.         // Covariance matrix
  222.         final RealMatrix noiseK = MatrixUtils.createRealMatrix(nbDyn + nbMeas, nbDyn + nbMeas);
  223.         final RealMatrix noiseP = covarianceMatrixProvider.getInitialCovarianceMatrix(nominalMeanSpacecraftState);
  224.         noiseK.setSubMatrix(noiseP.getData(), 0, 0);
  225.         if (measurementProcessNoiseMatrix != null) {
  226.             final RealMatrix noiseM = measurementProcessNoiseMatrix.getInitialCovarianceMatrix(nominalMeanSpacecraftState);
  227.             noiseK.setSubMatrix(noiseM.getData(), nbDyn, nbDyn);
  228.         }

  229.         // Verify dimension
  230.         KalmanEstimatorUtil.checkDimension(noiseK.getRowDimension(),
  231.                                            builder.getOrbitalParametersDrivers(),
  232.                                            builder.getPropagationParametersDrivers(),
  233.                                            estimatedMeasurementsParameters);

  234.         final RealMatrix correctedCovariance = KalmanEstimatorUtil.normalizeCovarianceMatrix(noiseK, scale);

  235.         // Initialize corrected estimate
  236.         this.correctedEstimate = new ProcessEstimate(0.0, correctedFilterCorrection, correctedCovariance);

  237.     }

  238.     /** {@inheritDoc} */
  239.     @Override
  240.     public KalmanObserver getObserver() {
  241.         return observer;
  242.     }

  243.     /** Set the observer.
  244.      * @param observer the observer
  245.      */
  246.     public void setObserver(final KalmanObserver observer) {
  247.         this.observer = observer;
  248.     }

  249.     /** Get the current corrected estimate.
  250.      * @return current corrected estimate
  251.      */
  252.     public ProcessEstimate getEstimate() {
  253.         return correctedEstimate;
  254.     }

  255.     /** Getter for the scale.
  256.      * @return the scale
  257.      */
  258.     protected double[] getScale() {
  259.         return scale;
  260.     }

  261.     /** Process a single measurement.
  262.      * <p>
  263.      * Update the filter with the new measurements.
  264.      * </p>
  265.      * @param observedMeasurements the list of measurements to process
  266.      * @param filter Extended Kalman Filter
  267.      * @return estimated propagator
  268.      */
  269.     public DSSTPropagator processMeasurements(final List<ObservedMeasurement<?>> observedMeasurements,
  270.                                               final ExtendedKalmanFilter<MeasurementDecorator> filter) {
  271.         try {

  272.             // Sort the measurement
  273.             observedMeasurements.sort(new ChronologicalComparator());
  274.             final AbsoluteDate tStart             = observedMeasurements.get(0).getDate();
  275.             final AbsoluteDate tEnd               = observedMeasurements.get(observedMeasurements.size() - 1).getDate();
  276.             final double       overshootTimeRange = FastMath.nextAfter(tEnd.durationFrom(tStart),
  277.                                                     Double.POSITIVE_INFINITY);

  278.             // Initialize step handler and set it to the propagator
  279.             final SemiAnalyticalMeasurementHandler stepHandler = new SemiAnalyticalMeasurementHandler(this, filter, observedMeasurements, builder.getInitialOrbitDate());
  280.             dsstPropagator.getMultiplexer().add(stepHandler);
  281.             dsstPropagator.propagate(tStart, tStart.shiftedBy(overshootTimeRange));

  282.             // Return the last estimated propagator
  283.             return getEstimatedPropagator();

  284.         } catch (MathRuntimeException mrte) {
  285.             throw new OrekitException(mrte);
  286.         }
  287.     }

  288.     /** Get the propagator estimated with the values set in the propagator builder.
  289.      * @return propagator based on the current values in the builder
  290.      */
  291.     public DSSTPropagator getEstimatedPropagator() {
  292.         // Return propagator built with current instantiation of the propagator builder
  293.         return (DSSTPropagator) builder.buildPropagator();
  294.     }

  295.     /** {@inheritDoc} */
  296.     @Override
  297.     public NonLinearEvolution getEvolution(final double previousTime, final RealVector previousState,
  298.                                            final MeasurementDecorator measurement) {

  299.         // Set a reference date for all measurements parameters that lack one (including the not estimated ones)
  300.         final ObservedMeasurement<?> observedMeasurement = measurement.getObservedMeasurement();
  301.         for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
  302.             if (driver.getReferenceDate() == null) {
  303.                 driver.setReferenceDate(builder.getInitialOrbitDate());
  304.             }
  305.         }

  306.         // Increment measurement number
  307.         ++currentMeasurementNumber;

  308.         // Update the current date
  309.         currentDate = measurement.getObservedMeasurement().getDate();

  310.         // Normalized state transition matrix
  311.         final RealMatrix stm = getErrorStateTransitionMatrix();

  312.         // Predict filter correction
  313.         predictedFilterCorrection = predictFilterCorrection(stm);

  314.         // Short period term derivatives
  315.         analyticalDerivativeComputations(nominalMeanSpacecraftState);

  316.         // Calculate the predicted osculating elements
  317.         final double[] osculating = computeOsculatingElements(predictedFilterCorrection);
  318.         final Orbit osculatingOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(osculating, null, builder.getPositionAngleType(),
  319.                                                                             currentDate, nominalMeanSpacecraftState.getOrbit().getMu(),
  320.                                                                             nominalMeanSpacecraftState.getFrame());

  321.         // Compute the predicted measurements  (See Ref [1], Eq. 3.8)
  322.         predictedMeasurement = observedMeasurement.estimate(currentMeasurementNumber,
  323.                                                             currentMeasurementNumber,
  324.                                                             new SpacecraftState[] {
  325.                                                                 new SpacecraftState(osculatingOrbit,
  326.                                                                                     nominalMeanSpacecraftState.getAttitude(),
  327.                                                                                     nominalMeanSpacecraftState.getMass(),
  328.                                                                                     nominalMeanSpacecraftState.getAdditionalDataValues(),
  329.                                                                                     nominalMeanSpacecraftState.getAdditionalStatesDerivatives())
  330.                                                             });

  331.         // Normalized measurement matrix
  332.         final RealMatrix measurementMatrix = getMeasurementMatrix();

  333.         // Number of estimated measurement parameters
  334.         final int nbMeas = getNumberSelectedMeasurementDriversValuesToEstimate();

  335.         // Number of estimated dynamic parameters (orbital + propagation)
  336.         final int nbDyn  = getNumberSelectedOrbitalDriversValuesToEstimate() + getNumberSelectedPropagationDriversValuesToEstimate();

  337.         // Covariance matrix
  338.         final RealMatrix noiseK = MatrixUtils.createRealMatrix(nbDyn + nbMeas, nbDyn + nbMeas);
  339.         final RealMatrix noiseP = covarianceMatrixProvider.getProcessNoiseMatrix(previousNominalMeanSpacecraftState, nominalMeanSpacecraftState);
  340.         noiseK.setSubMatrix(noiseP.getData(), 0, 0);
  341.         if (measurementProcessNoiseMatrix != null) {
  342.             final RealMatrix noiseM = measurementProcessNoiseMatrix.getProcessNoiseMatrix(previousNominalMeanSpacecraftState, nominalMeanSpacecraftState);
  343.             noiseK.setSubMatrix(noiseM.getData(), nbDyn, nbDyn);
  344.         }

  345.         // Verify dimension
  346.         KalmanEstimatorUtil.checkDimension(noiseK.getRowDimension(),
  347.                                            builder.getOrbitalParametersDrivers(),
  348.                                            builder.getPropagationParametersDrivers(),
  349.                                            estimatedMeasurementsParameters);

  350.         final RealMatrix normalizedProcessNoise = KalmanEstimatorUtil.normalizeCovarianceMatrix(noiseK, scale);

  351.         // Return
  352.         return new NonLinearEvolution(measurement.getTime(), predictedFilterCorrection, stm,
  353.                                       normalizedProcessNoise, measurementMatrix);
  354.     }

  355.     /** {@inheritDoc} */
  356.     @Override
  357.     public RealVector getInnovation(final MeasurementDecorator measurement, final NonLinearEvolution evolution,
  358.                                     final RealMatrix innovationCovarianceMatrix) {

  359.         // Apply the dynamic outlier filter, if it exists
  360.         KalmanEstimatorUtil.applyDynamicOutlierFilter(predictedMeasurement, innovationCovarianceMatrix);
  361.         // Compute the innovation vector
  362.         return KalmanEstimatorUtil.computeInnovationVector(predictedMeasurement, predictedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
  363.     }

  364.     /** {@inheritDoc} */
  365.     @Override
  366.     public void finalizeEstimation(final ObservedMeasurement<?> observedMeasurement,
  367.                                    final ProcessEstimate estimate) {
  368.         // Update the process estimate
  369.         correctedEstimate = estimate;
  370.         // Corrected filter correction
  371.         correctedFilterCorrection = estimate.getState();
  372.         // Update the previous nominal mean spacecraft state
  373.         previousNominalMeanSpacecraftState = nominalMeanSpacecraftState;
  374.         // Calculate the corrected osculating elements
  375.         final double[] osculating = computeOsculatingElements(correctedFilterCorrection);
  376.         final Orbit osculatingOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(osculating, null, builder.getPositionAngleType(),
  377.                                                                             currentDate, nominalMeanSpacecraftState.getOrbit().getMu(),
  378.                                                                             nominalMeanSpacecraftState.getFrame());

  379.         // Compute the corrected measurements
  380.         correctedMeasurement = observedMeasurement.estimate(currentMeasurementNumber,
  381.                                                             currentMeasurementNumber,
  382.                                                             new SpacecraftState[] {
  383.                                                                 new SpacecraftState(osculatingOrbit,
  384.                                                                                     nominalMeanSpacecraftState.getAttitude(),
  385.                                                                                     nominalMeanSpacecraftState.getMass(),
  386.                                                                                     nominalMeanSpacecraftState.getAdditionalDataValues(),
  387.                                                                                     nominalMeanSpacecraftState.getAdditionalStatesDerivatives())
  388.                                                             });
  389.         // Call the observer if the user add one
  390.         if (observer != null) {
  391.             observer.evaluationPerformed(this);
  392.         }
  393.     }

  394.     /** {@inheritDoc} */
  395.     @Override
  396.     public void finalizeOperationsObservationGrid() {
  397.         // Update parameters
  398.         updateParameters();
  399.     }

  400.     /** {@inheritDoc} */
  401.     @Override
  402.     public ParameterDriversList getEstimatedOrbitalParameters() {
  403.         return estimatedOrbitalParameters;
  404.     }

  405.     /** {@inheritDoc} */
  406.     @Override
  407.     public ParameterDriversList getEstimatedPropagationParameters() {
  408.         return estimatedPropagationParameters;
  409.     }

  410.     /** {@inheritDoc} */
  411.     @Override
  412.     public ParameterDriversList getEstimatedMeasurementsParameters() {
  413.         return estimatedMeasurementsParameters;
  414.     }

  415.     /** {@inheritDoc} */
  416.     @Override
  417.     public SpacecraftState[] getPredictedSpacecraftStates() {
  418.         return new SpacecraftState[] {nominalMeanSpacecraftState};
  419.     }

  420.     /** {@inheritDoc} */
  421.     @Override
  422.     public SpacecraftState[] getCorrectedSpacecraftStates() {
  423.         return new SpacecraftState[] {getEstimatedPropagator().getInitialState()};
  424.     }

  425.     /** {@inheritDoc} */
  426.     @Override
  427.     public RealVector getPhysicalEstimatedState() {
  428.         // Method {@link ParameterDriver#getValue()} is used to get
  429.         // the physical values of the state.
  430.         // The scales'array is used to get the size of the state vector
  431.         final RealVector physicalEstimatedState = new ArrayRealVector(scale.length);
  432.         int i = 0;
  433.         for (final DelegatingDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
  434.             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
  435.                 physicalEstimatedState.setEntry(i++, span.getData());
  436.             }
  437.         }
  438.         for (final DelegatingDriver driver : getEstimatedPropagationParameters().getDrivers()) {
  439.             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
  440.                 physicalEstimatedState.setEntry(i++, span.getData());
  441.             }
  442.         }
  443.         for (final DelegatingDriver driver : getEstimatedMeasurementsParameters().getDrivers()) {
  444.             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
  445.                 physicalEstimatedState.setEntry(i++, span.getData());
  446.             }
  447.         }

  448.         return physicalEstimatedState;
  449.     }

  450.     /** {@inheritDoc} */
  451.     @Override
  452.     public RealMatrix getPhysicalEstimatedCovarianceMatrix() {
  453.         // Un-normalize the estimated covariance matrix (P) from Hipparchus and return it.
  454.         // The covariance P is an mxm matrix where m = nbOrb + nbPropag + nbMeas
  455.         // For each element [i,j] of P the corresponding normalized value is:
  456.         // Pn[i,j] = P[i,j] / (scale[i]*scale[j])
  457.         // Consequently: P[i,j] = Pn[i,j] * scale[i] * scale[j]
  458.         return KalmanEstimatorUtil.unnormalizeCovarianceMatrix(correctedEstimate.getCovariance(), scale);
  459.     }

  460.     /** {@inheritDoc} */
  461.     @Override
  462.     public RealMatrix getPhysicalStateTransitionMatrix() {
  463.         //  Un-normalize the state transition matrix (φ) from Hipparchus and return it.
  464.         // φ is an mxm matrix where m = nbOrb + nbPropag + nbMeas
  465.         // For each element [i,j] of normalized φ (φn), the corresponding physical value is:
  466.         // φ[i,j] = φn[i,j] * scale[i] / scale[j]
  467.         return correctedEstimate.getStateTransitionMatrix() == null ?
  468.                 null : KalmanEstimatorUtil.unnormalizeStateTransitionMatrix(correctedEstimate.getStateTransitionMatrix(), scale);
  469.     }

  470.     /** {@inheritDoc} */
  471.     @Override
  472.     public RealMatrix getPhysicalMeasurementJacobian() {
  473.         // Un-normalize the measurement matrix (H) from Hipparchus and return it.
  474.         // H is an nxm matrix where:
  475.         //  - m = nbOrb + nbPropag + nbMeas is the number of estimated parameters
  476.         //  - n is the size of the measurement being processed by the filter
  477.         // For each element [i,j] of normalized H (Hn) the corresponding physical value is:
  478.         // H[i,j] = Hn[i,j] * σ[i] / scale[j]
  479.         return correctedEstimate.getMeasurementJacobian() == null ?
  480.                 null : KalmanEstimatorUtil.unnormalizeMeasurementJacobian(correctedEstimate.getMeasurementJacobian(),
  481.                                                                           scale,
  482.                                                                           correctedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
  483.     }

  484.     /** {@inheritDoc} */
  485.     @Override
  486.     public RealMatrix getPhysicalInnovationCovarianceMatrix() {
  487.         // Un-normalize the innovation covariance matrix (S) from Hipparchus and return it.
  488.         // S is an nxn matrix where n is the size of the measurement being processed by the filter
  489.         // For each element [i,j] of normalized S (Sn) the corresponding physical value is:
  490.         // S[i,j] = Sn[i,j] * σ[i] * σ[j]
  491.         return correctedEstimate.getInnovationCovariance() == null ?
  492.                 null : KalmanEstimatorUtil.unnormalizeInnovationCovarianceMatrix(correctedEstimate.getInnovationCovariance(),
  493.                                                                                  predictedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
  494.     }

  495.     /** {@inheritDoc} */
  496.     @Override
  497.     public RealMatrix getPhysicalKalmanGain() {
  498.         // Un-normalize the Kalman gain (K) from Hipparchus and return it.
  499.         // K is an mxn matrix where:
  500.         //  - m = nbOrb + nbPropag + nbMeas is the number of estimated parameters
  501.         //  - n is the size of the measurement being processed by the filter
  502.         // For each element [i,j] of normalized K (Kn) the corresponding physical value is:
  503.         // K[i,j] = Kn[i,j] * scale[i] / σ[j]
  504.         return correctedEstimate.getKalmanGain() == null ?
  505.                 null : KalmanEstimatorUtil.unnormalizeKalmanGainMatrix(correctedEstimate.getKalmanGain(),
  506.                                                                        scale,
  507.                                                                        correctedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation());
  508.     }

  509.     /** {@inheritDoc} */
  510.     @Override
  511.     public int getCurrentMeasurementNumber() {
  512.         return currentMeasurementNumber;
  513.     }

  514.     /** {@inheritDoc} */
  515.     @Override
  516.     public AbsoluteDate getCurrentDate() {
  517.         return currentDate;
  518.     }

  519.     /** {@inheritDoc} */
  520.     @Override
  521.     public EstimatedMeasurement<?> getPredictedMeasurement() {
  522.         return predictedMeasurement;
  523.     }

  524.     /** {@inheritDoc} */
  525.     @Override
  526.     public EstimatedMeasurement<?> getCorrectedMeasurement() {
  527.         return correctedMeasurement;
  528.     }

  529.     /** {@inheritDoc} */
  530.     @Override
  531.     public void updateNominalSpacecraftState(final SpacecraftState nominal) {
  532.         this.nominalMeanSpacecraftState = nominal;
  533.         // Update the builder with the nominal mean elements orbit
  534.         builder.resetOrbit(nominal.getOrbit(), PropagationType.MEAN);

  535.         // Additionally, update the builder with the predicted mass value.
  536.         // If any mass changes have occurred during this estimation step, such as maneuvers,
  537.         // the updated mass value must be carried over so that new Propagators from this builder start with the updated mass.
  538.         builder.setMass(nominal.getMass());
  539.     }

  540.     /** Update the reference trajectories using the propagator as input.
  541.      * @param propagator The new propagator to use
  542.      */
  543.     public void updateReferenceTrajectory(final DSSTPropagator propagator) {

  544.         dsstPropagator = propagator;

  545.         // Equation name
  546.         final String equationName = SemiAnalyticalKalmanEstimator.class.getName() + "-derivatives-";

  547.         // Mean state
  548.         final SpacecraftState meanState = dsstPropagator.initialIsOsculating() ?
  549.                        DSSTPropagator.computeMeanState(dsstPropagator.getInitialState(), dsstPropagator.getAttitudeProvider(), dsstPropagator.getAllForceModels()) :
  550.                        dsstPropagator.getInitialState();

  551.         // Update the jacobian harvester
  552.         dsstPropagator.setInitialState(meanState, PropagationType.MEAN);
  553.         harvester = dsstPropagator.setupMatricesComputation(equationName, null, null);

  554.     }

  555.     /** {@inheritDoc} */
  556.     @Override
  557.     public void updateShortPeriods(final SpacecraftState state) {
  558.         // Loop on DSST force models
  559.         for (final DSSTForceModel model : builder.getAllForceModels()) {
  560.             model.updateShortPeriodTerms(model.getParametersAllValues(), state);
  561.         }
  562.         harvester.updateFieldShortPeriodTerms(state);
  563.     }

  564.     /** {@inheritDoc} */
  565.     @Override
  566.     public void initializeShortPeriodicTerms(final SpacecraftState meanState) {
  567.         final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<>();
  568.         // initialize ForceModels in OSCULATING mode even if propagation is MEAN
  569.         final PropagationType type = PropagationType.OSCULATING;
  570.         for (final DSSTForceModel force :  builder.getAllForceModels()) {
  571.             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(new AuxiliaryElements(meanState.getOrbit(), 1), type, force.getParameters(meanState.getDate())));
  572.         }
  573.         dsstPropagator.setShortPeriodTerms(shortPeriodTerms);
  574.         // also need to initialize the Field terms in the same mode
  575.         harvester.initializeFieldShortPeriodTerms(meanState, type);
  576.     }

  577.     /** Get the normalized state transition matrix (STM) from previous point to current point.
  578.      * The STM contains the partial derivatives of current state with respect to previous state.
  579.      * The  STM is an mxm matrix where m is the size of the state vector.
  580.      * m = nbOrb + nbPropag + nbMeas
  581.      * @return the normalized error state transition matrix
  582.      */
  583.     private RealMatrix getErrorStateTransitionMatrix() {

  584.         /* The state transition matrix is obtained as follows, with:
  585.          *  - Phi(k, k-1) : Transitional orbital matrix
  586.          *  - Psi(k, k-1) : Transitional propagation parameters matrix
  587.          *
  588.          *       |             |             |   .    |
  589.          *       | Phi(k, k-1) | Psi(k, k-1) | ..0..  |
  590.          *       |             |             |   .    |
  591.          *       |-------------|-------------|--------|
  592.          *       |      .      |    1 0 0    |   .    |
  593.          * STM = |    ..0..    |    0 1 0    | ..0..  |
  594.          *       |      .      |    0 0 1    |   .    |
  595.          *       |-------------|-------------|--------|
  596.          *       |      .      |      .      | 1 0 0..|
  597.          *       |    ..0..    |    ..0..    | 0 1 0..|
  598.          *       |      .      |      .      | 0 0 1..|
  599.          */

  600.         // Initialize to the proper size identity matrix
  601.         final RealMatrix stm = MatrixUtils.createRealIdentityMatrix(correctedEstimate.getState().getDimension());

  602.         // Derivatives of the state vector with respect to initial state vector
  603.         final int nbOrb = getNumberSelectedOrbitalDriversValuesToEstimate();
  604.         final RealMatrix dYdY0 = harvester.getB2(nominalMeanSpacecraftState);

  605.         // Calculate transitional orbital matrix (See Ref [1], Eq. 3.4a)
  606.         final RealMatrix phi = dYdY0.multiply(phiS);

  607.         // Fill the state transition matrix with the orbital drivers
  608.         final List<DelegatingDriver> drivers = builder.getOrbitalParametersDrivers().getDrivers();
  609.         for (int i = 0; i < nbOrb; ++i) {
  610.             if (drivers.get(i).isSelected()) {
  611.                 int jOrb = 0;
  612.                 for (int j = 0; j < nbOrb; ++j) {
  613.                     if (drivers.get(j).isSelected()) {
  614.                         stm.setEntry(i, jOrb++, phi.getEntry(i, j));
  615.                     }
  616.                 }
  617.             }
  618.         }

  619.         // Update PhiS
  620.         phiS = new QRDecomposition(dYdY0).getSolver().getInverse();

  621.         // Derivatives of the state vector with respect to propagation parameters
  622.         if (psiS != null) {

  623.             final int nbProp = getNumberSelectedPropagationDriversValuesToEstimate();
  624.             final RealMatrix dYdPp = harvester.getB3(nominalMeanSpacecraftState);

  625.             // Calculate transitional parameters matrix (See Ref [1], Eq. 3.4b)
  626.             final RealMatrix psi = dYdPp.subtract(phi.multiply(psiS));

  627.             // Fill 1st row, 2nd column (dY/dPp)
  628.             for (int i = 0; i < nbOrb; ++i) {
  629.                 for (int j = 0; j < nbProp; ++j) {
  630.                     stm.setEntry(i, j + nbOrb, psi.getEntry(i, j));
  631.                 }
  632.             }

  633.             // Update PsiS
  634.             psiS = dYdPp;

  635.         }

  636.         // Normalization of the STM
  637.         // normalized(STM)ij = STMij*Sj/Si
  638.         for (int i = 0; i < scale.length; i++) {
  639.             for (int j = 0; j < scale.length; j++ ) {
  640.                 stm.setEntry(i, j, stm.getEntry(i, j) * scale[j] / scale[i]);
  641.             }
  642.         }

  643.         // Return the error state transition matrix
  644.         return stm;

  645.     }

  646.     /** Get the normalized measurement matrix H.
  647.      * H contains the partial derivatives of the measurement with respect to the state.
  648.      * H is an nxm matrix where n is the size of the measurement vector and m the size of the state vector.
  649.      * @return the normalized measurement matrix H
  650.      */
  651.     private RealMatrix getMeasurementMatrix() {

  652.         // Observed measurement characteristics
  653.         final SpacecraftState        evaluationState     = predictedMeasurement.getStates()[0];
  654.         final ObservedMeasurement<?> observedMeasurement = predictedMeasurement.getObservedMeasurement();
  655.         final double[] sigma  = predictedMeasurement.getObservedMeasurement().getTheoreticalStandardDeviation();

  656.         // Initialize measurement matrix H: nxm
  657.         // n: Number of measurements in current measurement
  658.         // m: State vector size
  659.         final RealMatrix measurementMatrix = MatrixUtils.
  660.                 createRealMatrix(observedMeasurement.getDimension(),
  661.                                  correctedEstimate.getState().getDimension());

  662.         // Predicted orbit
  663.         final Orbit predictedOrbit = evaluationState.getOrbit();

  664.         // Measurement matrix's columns related to orbital and propagation parameters
  665.         // ----------------------------------------------------------

  666.         // Partial derivatives of the current Cartesian coordinates with respect to current orbital state
  667.         final int nbOrb  = getNumberSelectedOrbitalDrivers();
  668.         final int nbProp = getNumberSelectedPropagationDrivers();
  669.         final double[][] aCY = new double[nbOrb][nbOrb];
  670.         predictedOrbit.getJacobianWrtParameters(builder.getPositionAngleType(), aCY);
  671.         final RealMatrix dCdY = new Array2DRowRealMatrix(aCY, false);

  672.         // Jacobian of the measurement with respect to current Cartesian coordinates
  673.         final RealMatrix dMdC = new Array2DRowRealMatrix(predictedMeasurement.getStateDerivatives(0), false);

  674.         // Jacobian of the measurement with respect to current orbital state
  675.         RealMatrix dMdY = dMdC.multiply(dCdY);

  676.         // Compute factor dShortPeriod_dMeanState = I+B1 | B4
  677.         final RealMatrix IpB1B4 = MatrixUtils.createRealMatrix(nbOrb, nbOrb + nbProp);

  678.         // B1
  679.         final RealMatrix B1 = harvester.getB1();

  680.         // I + B1
  681.         final RealMatrix I = MatrixUtils.createRealIdentityMatrix(nbOrb);
  682.         final RealMatrix IpB1 = I.add(B1);
  683.         IpB1B4.setSubMatrix(IpB1.getData(), 0, 0);

  684.         // If there are not propagation parameters, B4 is null
  685.         if (psiS != null) {
  686.             final RealMatrix B4 = harvester.getB4();
  687.             IpB1B4.setSubMatrix(B4.getData(), 0, nbOrb);
  688.         }

  689.         // Ref [1], Eq. 3.10
  690.         dMdY = dMdY.multiply(IpB1B4);

  691.         for (int i = 0; i < dMdY.getRowDimension(); i++) {
  692.             for (int j = 0; j < nbOrb; j++) {
  693.                 final double driverScale = builder.getOrbitalParametersDrivers().getDrivers().get(j).getScale();
  694.                 measurementMatrix.setEntry(i, j, dMdY.getEntry(i, j) / sigma[i] * driverScale);
  695.             }

  696.             int col = 0;
  697.             for (int j = 0; j < nbProp; j++) {
  698.                 final double driverScale = estimatedPropagationParameters.getDrivers().get(j).getScale();
  699.                 for (Span<Double> span = estimatedPropagationParameters.getDrivers().get(j).getValueSpanMap().getFirstSpan();
  700.                                   span != null; span = span.next()) {

  701.                     measurementMatrix.setEntry(i, col + nbOrb,
  702.                                                dMdY.getEntry(i, col + nbOrb) / sigma[i] * driverScale);
  703.                     col++;
  704.                 }
  705.             }
  706.         }

  707.         // Normalized measurement matrix's columns related to measurement parameters
  708.         // --------------------------------------------------------------

  709.         // Jacobian of the measurement with respect to measurement parameters
  710.         // Gather the measurement parameters linked to current measurement
  711.         for (final ParameterDriver driver : observedMeasurement.getParametersDrivers()) {
  712.             if (driver.isSelected()) {
  713.                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  714.                     // Derivatives of current measurement w/r to selected measurement parameter
  715.                     final double[] aMPm = predictedMeasurement.getParameterDerivatives(driver, span.getStart());

  716.                     // Check that the measurement parameter is managed by the filter
  717.                     if (measurementParameterColumns.get(span.getData()) != null) {
  718.                         // Column of the driver in the measurement matrix
  719.                         final int driverColumn = measurementParameterColumns.get(span.getData());

  720.                         // Fill the corresponding indexes of the measurement matrix
  721.                         for (int i = 0; i < aMPm.length; ++i) {
  722.                             measurementMatrix.setEntry(i, driverColumn, aMPm[i] / sigma[i] * driver.getScale());
  723.                         }
  724.                     }
  725.                 }
  726.             }
  727.         }

  728.         return measurementMatrix;
  729.     }

  730.     /** Predict the filter correction for the new observation.
  731.      * @param stm normalized state transition matrix
  732.      * @return the predicted filter correction for the new observation
  733.      */
  734.     private RealVector predictFilterCorrection(final RealMatrix stm) {
  735.         // Ref [1], Eq. 3.5a and 3.5b
  736.         return stm.operate(correctedFilterCorrection);
  737.     }

  738.     /** Compute the predicted osculating elements.
  739.      * @param filterCorrection kalman filter correction
  740.      * @return the predicted osculating element
  741.      */
  742.     private double[] computeOsculatingElements(final RealVector filterCorrection) {

  743.         // Number of estimated orbital parameters
  744.         final int nbOrb = getNumberSelectedOrbitalDrivers();

  745.         // B1
  746.         final RealMatrix B1 = harvester.getB1();

  747.         // Short periodic terms
  748.         final double[] shortPeriodTerms = dsstPropagator.getShortPeriodTermsValue(nominalMeanSpacecraftState);

  749.         // Physical filter correction
  750.         final RealVector physicalFilterCorrection = MatrixUtils.createRealVector(nbOrb);
  751.         for (int index = 0; index < nbOrb; index++) {
  752.             physicalFilterCorrection.addToEntry(index, filterCorrection.getEntry(index) * scale[index]);
  753.         }

  754.         // B1 * physicalCorrection
  755.         final RealVector B1Correction = B1.operate(physicalFilterCorrection);

  756.         // Nominal mean elements
  757.         final double[] nominalMeanElements = new double[nbOrb];
  758.         OrbitType.EQUINOCTIAL.mapOrbitToArray(nominalMeanSpacecraftState.getOrbit(), builder.getPositionAngleType(), nominalMeanElements, null);

  759.         // Ref [1] Eq. 3.6
  760.         final double[] osculatingElements = new double[nbOrb];
  761.         for (int i = 0; i < nbOrb; i++) {
  762.             osculatingElements[i] = nominalMeanElements[i] +
  763.                                     physicalFilterCorrection.getEntry(i) +
  764.                                     shortPeriodTerms[i] +
  765.                                     B1Correction.getEntry(i);
  766.         }

  767.         // Return
  768.         return osculatingElements;

  769.     }

  770.     /** Analytical computation of derivatives.
  771.      * This method allow to compute analytical derivatives.
  772.      * @param state mean state used to calculate short period perturbations
  773.      */
  774.     private void analyticalDerivativeComputations(final SpacecraftState state) {
  775.         harvester.setReferenceState(state);
  776.     }

  777.     /** Get the number of estimated orbital parameters.
  778.      * @return the number of estimated orbital parameters
  779.      */
  780.     private int getNumberSelectedOrbitalDrivers() {
  781.         return estimatedOrbitalParameters.getNbParams();
  782.     }

  783.     /** Get the number of estimated propagation parameters.
  784.      * @return the number of estimated propagation parameters
  785.      */
  786.     private int getNumberSelectedPropagationDrivers() {
  787.         return estimatedPropagationParameters.getNbParams();
  788.     }

  789.     /** Get the number of estimated orbital parameters values (some parameter
  790.      * driver may have several values to estimate for different time range
  791.      * {@link ParameterDriver}.
  792.      * @return the number of estimated values for , orbital parameters
  793.      */
  794.     private int getNumberSelectedOrbitalDriversValuesToEstimate() {
  795.         int nbOrbitalValuesToEstimate = 0;
  796.         for (final ParameterDriver driver : estimatedOrbitalParameters.getDrivers()) {
  797.             nbOrbitalValuesToEstimate += driver.getNbOfValues();
  798.         }
  799.         return nbOrbitalValuesToEstimate;
  800.     }

  801.     /** Get the number of estimated propagation parameters values (some parameter
  802.      * driver may have several values to estimate for different time range
  803.      * {@link ParameterDriver}.
  804.      * @return the number of estimated values for propagation parameters
  805.      */
  806.     private int getNumberSelectedPropagationDriversValuesToEstimate() {
  807.         int nbPropagationValuesToEstimate = 0;
  808.         for (final ParameterDriver driver : estimatedPropagationParameters.getDrivers()) {
  809.             nbPropagationValuesToEstimate += driver.getNbOfValues();
  810.         }
  811.         return nbPropagationValuesToEstimate;
  812.     }

  813.     /** Get the number of estimated measurement parameters values (some parameter
  814.      * driver may have several values to estimate for different time range
  815.      * {@link ParameterDriver}.
  816.      * @return the number of estimated values for measurement parameters
  817.      */
  818.     private int getNumberSelectedMeasurementDriversValuesToEstimate() {
  819.         int nbMeasurementValuesToEstimate = 0;
  820.         for (final ParameterDriver driver : estimatedMeasurementsParameters.getDrivers()) {
  821.             nbMeasurementValuesToEstimate += driver.getNbOfValues();
  822.         }
  823.         return nbMeasurementValuesToEstimate;
  824.     }

  825.     /** Update the estimated parameters after the correction phase of the filter.
  826.      * The min/max allowed values are handled by the parameter themselves.
  827.      */
  828.     private void updateParameters() {
  829.         final RealVector correctedState = correctedEstimate.getState();
  830.         int i = 0;
  831.         // Orbital parameters
  832.         for (final DelegatingDriver driver : getEstimatedOrbitalParameters().getDrivers()) {
  833.             // let the parameter handle min/max clipping
  834.             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
  835.                 driver.setNormalizedValue(driver.getNormalizedValue(span.getStart()) + correctedState.getEntry(i++), span.getStart());
  836.             }
  837.         }

  838.         // Propagation parameters
  839.         for (final DelegatingDriver driver : getEstimatedPropagationParameters().getDrivers()) {
  840.             // let the parameter handle min/max clipping
  841.             // If the parameter driver contains only 1 value to estimate over the all time range
  842.             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
  843.                 driver.setNormalizedValue(driver.getNormalizedValue(span.getStart()) + correctedState.getEntry(i++), span.getStart());
  844.             }
  845.         }

  846.         // Measurements parameters
  847.         for (final DelegatingDriver driver : getEstimatedMeasurementsParameters().getDrivers()) {
  848.             // let the parameter handle min/max clipping
  849.             for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
  850.                 driver.setNormalizedValue(driver.getNormalizedValue(span.getStart()) + correctedState.getEntry(i++), span.getStart());
  851.             }
  852.         }
  853.     }

  854. }